API接口并发请求控制

一、问题概述

API接口,承载某重要业务,希望控制任意时间点的并发访问数在5以内,该如何实现?

二、解决思路

解决这个问题主要有4个思路: 原子计数器和信号量以及阻塞队列 , 固定线程池。
原子计数器有2个实现:AtomicInteger、LongAdder
信号量:Semaphore
阻塞队列:ArrayBlockingQueue、LinkedBlockingQueue
固定线程池: Executors.newFixedThreadPool

单元测试代码验证

1. AtomicInteger


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang3.RandomUtils;
import org.junit.jupiter.api.Test;
import org.springframework.util.StopWatch;

import lombok.extern.slf4j.Slf4j;

/**
 * AomicInteger控制线程并发数
 * 
 */
@Slf4j
public class AtomicIntegerTest
{
    private AtomicInteger count = new AtomicInteger(0);
    
    /**
     * 最大并发数
     */
    private int max = 5;
    
    /**
     * 线程池方式测试
     * 
     * @throws InterruptedException
     * @see [类、类#方法、类#成员]
     */
    @Test
    public void test()
        throws InterruptedException
    {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        int id = 0;
        while (id++ < 100)
        {
            cachedThreadPool.execute(() -> runCall());
            TimeUnit.MILLISECONDS.sleep(RandomUtils.nextInt(100, 1000));
        }
        cachedThreadPool.shutdown();
        while (!cachedThreadPool.isTerminated()) // 保证任务全部执行完
        {
        }
    }
    
    private void runCall()
    {
        try
        {
            log.info("++++ 计数器自增:{}", count.incrementAndGet());
            if (count.get() > max)
            {
                log.info("✈✈✈✈✈ 请求用户过多,请稍后再试! ✈✈✈✈✈");
                return;
            }
            
            // 模拟耗时业务操作
            log.info("★★★★★★★★ 报名或抢购处理中★★★★★★★★");
            StopWatch clock = new StopWatch();
            clock.start();
            TimeUnit.MILLISECONDS.sleep(RandomUtils.nextInt(1000, 5000));
            clock.stop();
            log.info("运行 {} ms ---------------", clock.getLastTaskTimeMillis());
        }
        catch (InterruptedException e)
        {
            log.error(e.getMessage());
        }
        finally
        {
            log.info("---- 计数器自减:{}", count.decrementAndGet());
        }
    }
}


运行结果:
在这里插入图片描述

2. LongAdder


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;

import org.apache.commons.lang3.RandomUtils;
import org.junit.jupiter.api.Test;
import org.springframework.util.StopWatch;

import lombok.extern.slf4j.Slf4j;

/**
 * LongAdder控制线程并发数
 *
 */
@Slf4j
public class LongAdderTest
{
    private LongAdder count = new LongAdder();
    
    /**
     * 最大并发数
     */
    private int max = 5;
    
    /**
     * 线程池方式测试
     * 
     * @throws InterruptedException
     * @see [类、类#方法、类#成员]
     */
    @Test
    public void test()
        throws InterruptedException
    {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        int id = 0;
        while (id++ < 100)
        {
            cachedThreadPool.execute(() -> runCall());
            TimeUnit.MILLISECONDS.sleep(RandomUtils.nextInt(100, 1000));
        }
        cachedThreadPool.shutdown();
        while (!cachedThreadPool.isTerminated()) // 保证任务全部执行完
        {
        }
    }
    
    private void runCall()
    {
        try
        {
            count.increment();
            log.info("++++ 计数器自增:{}", count.sum());
            if (count.sum() > max)
            {
                log.info("✈✈✈✈✈ 请求用户过多,请稍后再试! ✈✈✈✈✈");
                return;
            }
            
            // 模拟耗时业务操作
            log.info("★★★★★★★★ 报名或抢购处理中★★★★★★★★");
            StopWatch clock = new StopWatch();
            clock.start();
            TimeUnit.MILLISECONDS.sleep(RandomUtils.nextInt(1000, 5000));
            clock.stop();
            log.info("运行 {} ms ---------------", clock.getLastTaskTimeMillis());
        }
        catch (InterruptedException e)
        {
            log.error(e.getMessage());
        }
        finally
        {
            count.decrement();
            log.info("---- 计数器自减:{}", count.sum());
        }
    }
}

运行结果:
在这里插入图片描述

3. Semaphore


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang3.RandomUtils;
import org.junit.jupiter.api.Test;
import org.springframework.util.StopWatch;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class SemaphoreTest
{
    private int max = 5;
    
    private Semaphore semaphore = new Semaphore(max, true);
    
    private AtomicInteger count = new AtomicInteger(0);
    
    /**
     * 线程池方式测试
     * 
     * @throws InterruptedException
     * @see [类、类#方法、类#成员]
     */
    @Test
    public void test()
        throws InterruptedException
    {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        int id = 0;
        while (id++ < 100)
        {
            cachedThreadPool.execute(() -> runCall());
            TimeUnit.MILLISECONDS.sleep(RandomUtils.nextInt(100, 1000));
        }
        cachedThreadPool.shutdown();
        while (!cachedThreadPool.isTerminated()) // 保证任务全部执行完
        {
        }
    }
    
    private void runCall()
    {
        try
        {
            semaphore.acquire();
            log.info("计数器自增:{}", count.incrementAndGet());
            
            // 模拟耗时业务操作
            log.info("★★★★★★★★ 报名或抢购处理中★★★★★★★★");
            StopWatch clock = new StopWatch();
            clock.start();
            TimeUnit.MILLISECONDS.sleep(RandomUtils.nextInt(1000, 5000));
            clock.stop();
            log.info("运行 {} ms ---------------", clock.getLastTaskTimeMillis());
        }
        catch (InterruptedException e)
        {
            log.error(e.getMessage());
        }
        finally
        {
            semaphore.release();
            log.info("计数器自减:{}", count.decrementAndGet());
        }
    }
}


运行结果:
在这里插入图片描述

4. BlockingQueue


import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang3.RandomUtils;
import org.junit.jupiter.api.Test;
import org.springframework.util.StopWatch;

import lombok.extern.slf4j.Slf4j;

/**
 * 阻塞队列控制并发<br>
 * https://www.cnblogs.com/WangHaiMing/p/8798709.html
 */
@Slf4j
public class BlockingQueueTest
{
    private int max = 5;
    
    private BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(max, true);
    // 或者 private BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(max, true);
    
    private AtomicInteger count = new AtomicInteger(0);
    
    /**
     * 线程池方式测试
     * 
     * @throws InterruptedException
     * @see [类、类#方法、类#成员]
     */
    @Test
    public void test()
        throws InterruptedException
    {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        int id = 0;
        while (id++ < 100)
        {
            cachedThreadPool.execute(() -> runCall());
            TimeUnit.MILLISECONDS.sleep(RandomUtils.nextInt(100, 1000));
        }
        cachedThreadPool.shutdown();
        while (!cachedThreadPool.isTerminated()) // 保证任务全部执行完
        {
        }
    }
    
    private void runCall()
    {
        try
        {
            log.info("计数器自增:{}", count.incrementAndGet());
            if (!blockingQueue.offer(""))
            {
                log.info("✈✈✈✈✈ 请求用户过多,请稍后再试! ✈✈✈✈✈");
                return;
            }
            
            // 模拟耗时业务操作
            log.info("★★★★★★★★ 报名或抢购处理中★★★★★★★★");
            StopWatch clock = new StopWatch();
            clock.start();
            TimeUnit.MILLISECONDS.sleep(RandomUtils.nextInt(1000, 5000));
            clock.stop();
            log.info("运行 {} ms ---------------", clock.getLastTaskTimeMillis());
            blockingQueue.poll();
        }
        catch (InterruptedException e)
        {
            log.error(e.getMessage());
        }
        finally
        {
            log.info("计数器自减:{}", count.decrementAndGet());
        }
    }
}


运行结果:
在这里插入图片描述

5. Executors.newFixedThreadPool


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang3.RandomUtils;
import org.junit.jupiter.api.Test;
import org.springframework.util.StopWatch;

import lombok.extern.slf4j.Slf4j;

/**
 * 固定长度线程池控制并发
 */
@Slf4j
public class FixedThreadPoolTest
{
    private int max = 5;
    
    private AtomicInteger count = new AtomicInteger(0);
    
    /**
     * 线程池方式测试
     * 
     * @throws InterruptedException
     * @see [类、类#方法、类#成员]
     */
    @Test
    public void test()
        throws InterruptedException
    {
        ExecutorService executorService = Executors.newFixedThreadPool(max);
        int id = 0;
        while (id++ < 100)
        {
            executorService.execute(() -> runCall());
            TimeUnit.MILLISECONDS.sleep(RandomUtils.nextInt(100, 1000));
        }
        executorService.shutdown();
        while (!executorService.isTerminated()) // 保证任务全部执行完
        {
        }
    }
    
    /**
     * 验证方法: sum('报名或抢购处理中')与sum('请求用户过多,请稍后再试')日志条数为100
     */
    private void runCall()
    {
        try
        {
            log.info("计数器自增:{}", count.incrementAndGet());
            
            // 模拟耗时业务操作
            log.info("★★★★★★★★ 报名或抢购处理中★★★★★★★★");
            StopWatch clock = new StopWatch();
            clock.start();
            TimeUnit.MILLISECONDS.sleep(RandomUtils.nextInt(1000, 5000));
            clock.stop();
            log.info("运行 {} ms ---------------", clock.getLastTaskTimeMillis());
        }
        catch (InterruptedException e)
        {
            log.error(e.getMessage());
        }
        finally
        {
            log.info("计数器自减:{}", count.decrementAndGet());
        }
    }
}

运行结果:
在这里插入图片描述

6. 实现区别

对比原子计数器和信号量的实现,我们可以发现信号量Semaphore一旦许可不够会导致线程阻塞,Executors.newFixedThreadPool也类似,其他实现,如原子计数器一旦达到最大线程并发数,可以快速失败,立即返回友好的提示信息。

三、API接口并发控制

1. 核心源码


import java.util.concurrent.Callable;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.fly.demo.JsonResult;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Api(tags = "接口并发控制")
@RestController
@RequestMapping(value = "/both", produces = "application/json; charset=utf-8")
public class ConcurrentController
{
    /**
     * 最大并发数
     */
    private int max = 5;
    
    /***************** 注意:不可共用计数器!!! ******************/
    
    AtomicInteger count1 = new AtomicInteger(0);
    
    LongAdder count2 = new LongAdder();
    
    LongAdder count3 = new LongAdder();
    
    Semaphore semaphore = new Semaphore(max, true);
    
    @ApiOperation("并发测试Atomic")
    @GetMapping("/query/atomic")
    public JsonResult<?> queryAtomic()
    {
        try
        {
            log.info("计数器自增:{}", count1.incrementAndGet());
            if (count1.get() > max)
            {
                log.info("✈✈✈✈✈ 请求用户过多✈✈✈✈✈");
                return JsonResult.error("请求用户过多,请稍后再试!");
            }
            log.info("业务处理开始......");
            TimeUnit.SECONDS.sleep(10);
        }
        catch (InterruptedException e)
        {
            log.error(e.getMessage());
        }
        finally
        {
            log.info("计数器自减:{}", count1.decrementAndGet());
        }
        return JsonResult.success();
    }
    
    @ApiOperation("并发测试LongAdder")
    @GetMapping("/query/longAdder")
    public JsonResult<?> queryLongAdder()
    {
        try
        {
            count2.increment();
            log.info("计数器自增:{}", count2.sum());
            if (count2.sum() > max)
            {
                log.info("✈✈✈✈✈ 请求用户过多,计数:{} ✈✈✈✈✈", count2.sum());
                return JsonResult.error("请求用户过多,请稍后再试!");
            }
            log.info("业务处理开始......");
            TimeUnit.SECONDS.sleep(10);
        }
        catch (InterruptedException e)
        {
            log.error(e.getMessage());
        }
        finally
        {
            count2.decrement();
            log.info("计数器自减:{}", count2.sum());
        }
        return JsonResult.success();
    }
    
    // 仅用于Semaphore中计数
    private AtomicInteger count = new AtomicInteger(0);
    
    @ApiOperation("并发测试Semaphore")
    @GetMapping("/query/semaphore")
    public JsonResult<?> querySemaphore()
    {
        try
        {
            // 一旦许可不够,线程阻塞
            semaphore.acquire();
            log.info("计数器自增:{}", count.incrementAndGet());
            
            log.info("业务处理开始......");
            TimeUnit.SECONDS.sleep(10);
        }
        catch (InterruptedException e)
        {
            log.error(e.getMessage());
        }
        finally
        {
            semaphore.release();
            log.info("计数器自减:{}", count.decrementAndGet());
        }
        return JsonResult.success();
    }
    
    @ApiOperation("并发测试InCallable")
    @GetMapping("/query/callable")
    public Callable<JsonResult<?>> callable()
    {
        return () -> {
            try
            {
                count3.increment();
                log.info("计数器自增:{}", count3.sum());
                if (count3.sum() > max)
                {
                    log.info("✈✈✈✈✈ 请求用户过多,计数:{} ✈✈✈✈✈", count3.sum());
                    return JsonResult.error("请求用户过多,请稍后再试!");
                }
                log.info("业务处理开始......");
                TimeUnit.SECONDS.sleep(10);
            }
            catch (InterruptedException e)
            {
                log.error(e.getMessage());
            }
            finally
            {
                count3.decrement();
                log.info("计数器自减:{}", count3.sum());
            }
            return JsonResult.success();
        };
    }
}

2. 源码放送

https://gitcode.com/00fly/springboot-demo.git

<think>我们正在讨论如何在Postman中实现多个接口并发请求。根据引用内容,有两种主要方法:使用CollectionRunner和直接配置并发设置。引用[1]提到:在Postman中,可以通过CollectionRunner来执行并发请求。CollectionRunner接收一个集合,并按照定义的方式运行集合中的请求。引用[2]提到:模拟并发是在收藏夹维度上选择接口进行并发测试的,并详细说明了步骤:新建收藏夹(集合)、添加请求、点击Run配置并发次数和请求间隔。因此,我们可以总结出以下步骤:方法一:使用CollectionRunner(集合运行器)1.创建一个集合(Collection),将需要并发请求接口添加到该集合中。2.打开CollectionRunner:点击集合旁边的“Run”按钮。3.在运行器界面,设置迭代次数(Iterations)和并发用户数(ConcurrentUsers)。4.点击“RunCollection”开始并发测试。方法二:直接通过集合的Run功能(与引用[2]描述一致)1.在收藏夹(即集合)下创建待并发测试的接口。2.点击收藏夹,然后选择“Run”。3.配置并发次数(迭代次数)和请求间隔(Delay)。4.执行并查看结果。注意:在Postman中,并发请求是通过设置多个用户同时运行集合来实现的,每个用户执行一次迭代(即运行集合中的所有请求)。所以,并发用户数就是同时运行的虚拟用户数。另外,需要说明的是,Postman的免费版可能对并发用户数有限制(例如最多支持50个并发用户),而付费版本(如PostmanPro)支持更高的并发。下面我们用一个简单的步骤说明:步骤1:创建集合-在Postman中,点击“Collections”->“CreateanewCollection”。-给集合命名,比如“并发测试集合”。步骤2:添加请求到集合-在集合中,可以添加多个请求(如GET、POST等)。-例如,添加两个请求:一个请求是获取用户信息(GET/users),另一个是创建用户(POST/users)。步骤3:运行集合并设置并发-选中集合,点击“Run”按钮。-在打开的CollectionRunner界面中,选择“RunConfiguration”选项卡。-设置:Iterations:10(表示总共运行10次,即每个请求运行10次)ConcurrentUsers:5(表示同时有5个用户运行,所以会分成2批,每批5个用户,总共10次迭代)Delay:0(请求之间的延迟,如果设置为0,则每个用户连续发送请求)步骤4:运行并查看结果-点击“RunCollection”按钮,开始测试。-测试完成后,可以查看每个请求的响应时间、状态码等统计信息。注意:并发请求会同时发送集合中的所有请求,但集合中的请求默认是按顺序执行的。如果希望集合中的请求并发执行(即同时发送多个不同的接口请求),则需要使用Postman的其他功能,比如在同一个迭代中同时发送多个请求,这可以通过在集合中使用脚本或者使用Postman的“SendRequestsinParallel”功能(注意:这个功能在CollectionRunner中默认是按顺序的,所以如果需要真正的并行,可能需要使用脚本或外部工具)。然而,根据引用[1]和[2]的描述,CollectionRunner设置并发用户数实际上是同时运行多个用户,每个用户按顺序执行集合中的请求。也就是说,在同一个用户的一次迭代中,请求是顺序执行的。但是多个用户同时运行,从而实现了并发的效果(多个用户同时请求,但每个用户内部顺序执行)。如果希望在一个用户的一次迭代中同时并发发送集合中的所有请求(即不按顺序,同时发送多个不同的接口请求),那么需要借助Postman的脚本功能(如使用`pm.sendRequest`)来异步发送请求。但这不在引用范围内,用户问题只要求多个接口并发请求,通常理解为多个用户同时访问(每个用户访问一个接口或顺序访问多个接口),所以使用CollectionRunner设置并发用户数即可满足需求。因此,针对用户的问题“在Postman中同时请求多个接口”,我们给出的方法是使用CollectionRunner设置并发用户数。相关问题:1.在Postman中如何设置集合中的请求同时发送(而不是顺序发送)?2.Postman的并发测试有哪些限制?3.除了Postman,还有哪些工具可以进行API并发测试?</think>### Postman 多个接口并发请求方法 在 Postman 中实现多个接口并发请求,主要通过 **Collection Runner** 功能实现,以下是详细步骤: --- #### **方法一:使用 Collection Runner(推荐)** 1. **创建请求集合** - 在左侧边栏点击 `+` 创建新集合(Collection) - 将需要并发测试的接口拖入集合中(如:登录接口、查询接口、支付接口) 2. **配置并发参数** - 选中集合 → 点击 **"Run"** 按钮 - 在运行器中设置: - **Iterations**:总请求次数(如 100 次) - **Concurrent Users**:并发用户数(如 10 个) - **Delay**:请求间隔(毫秒,可设为 0) ```plaintext [示例配置] Iterations = 100 Concurrent Users = 10 ➜ 表示 10 个用户同时执行,每个用户执行 10 次请求 ``` 3. **执行并发测试** - 点击 **"Run Collection"** 开始测试 - 结果面板会显示每个接口的响应时间、成功率等指标[^1][^2] --- #### **方法二:使用 Postman Monitors(定时自动并发)** 1. 进入 **"Monitors"** 标签页 2. 创建监控任务: - 选择目标集合 - 设置并发用户数(Concurrency) - 配置定时触发(如每 5 分钟执行一次) 3. 系统自动生成并发测试报告并邮件通知 --- #### ⚠️ **注意事项** 1. 免费版 Postman 的并发用户数上限为 **50 个** 2. 若需更高并发(如 1000+ 用户),需结合 **Postman CLI + Jenkins** 实现分布式测试 3. 关键参数建议: ```plaintext - 首次测试:从低并发开始(如 5 个用户) - 生产环境模拟:根据实际用户量设置 - 间隔时间(Delay):压力测试设为 0,模拟真实用户可设 100-500ms ``` --- #### **操作示意图** ```plaintext [集合创建] Postman 侧边栏 → Collections → "+" 新建 → 添加请求 [并发配置] 集合 → Run → ┌───────────────┐ │ Iterations: 100 │ │ Concurrent: 10 │ └───────────────┘ → Run Collection ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值