Bootstrap

【Java高并发调优系列代码实战】阻塞队列

1. 课程导读

本文演示的重点是阻塞队列BLockingQueue,实现将同步请求转为异步处理;

BLockingQueue是一个阻塞的队列,最典型的应用场景就是生产者和消费者模式。

生产者和消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此并不直接通信,而是通过阻塞队列进行通信,所以生产者生产完数据后不用等待消费者进行处理,而是直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列中获取数据,阻塞队列就相当于一个缓冲区,平衡生产者和消费者的处理能力。

 

在Java中该队列只是一个接口,它的实现类有ArrayBlockingQueue、DelayQueue、 LinkedBlockingDeque、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue等。

本文使用的是 LinkedBlockingQueue,废话不多说,上代码:

2. 示例代码

 2.1 依赖配置 

<dependency>

     <groupId>com.google.guava</groupId>

     <artifactId>guava</artifactId>

     <version>20.0</version>

 </dependency>

 2.2 控制层

@RequestMapping(value = "/v1/insertData")

public ResponseData insertData(@RequestBody JSONObject jsonObject) {

        BatchData batchData = new BatchData();

        batchData.setDataId(IdWorker.get32UUID());

        batchData.setTableNum(jsonObject.getString("tableNum"));

        batchData.setMessage(jsonObject.getString("message"));

        batchData.setCreateTime(LocalDateTime.now());

        //方式1.同步 入库

        //batchDataService.save(batchData);

        //方式2. 异步队列

        AsyncVo<BatchData> asyncVo = new AsyncVo<>();

        asyncVo.setParams(batchData);

        produceQueue.putQueue(asyncVo);

        return ResponseData.success();

}

 2.3 阻塞队列 (包名:package com.huigu.gpt.queue

生产者:

@Component

public class ProduceQueue {

    /**

     * 阻塞队列,设置缓冲容量为Integer.MAX_VALUE  不做容量限制

     */

    private static final BlockingQueue<AsyncVo<?>> queue = new LinkedBlockingQueue<>(Integer.MAX_VALUE);

    public BlockingQueue<AsyncVo<?>> getQueue() {

        return queue;

    }

    /**

     *  数据队列

     * @param vo

     */

    public void putQueue(AsyncVo<?> vo){

        try {

            queue.put(vo);

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

}

消费者:

@Component

@Slf4j

public class ConsumQuene extends Thread{



    @Autowired

    private ProduceQueue produceQueue;



    @Autowired

    private IBatchDataService batchDataService;



    private static boolean shutDown = true;



    public void setShutDown() {

        shutDown = false;

    }

    @Override

    public void run() {

        while (shutDown) {

            try {

                int queueSize = produceQueue.getQueue().size();

                if (queueSize != 0){

                    log.info("队列长度-> {}" , queueSize);

                }

                List<AsyncVo> asyncVoList = new ArrayList<>();

                //1.500条数据入库;2.等待100 毫秒,没达到500条进行入库  3. 没有数据会阻塞当前线程

                Queues.drain(produceQueue.getQueue(), asyncVoList, 500, 100, TimeUnit.MILLISECONDS);

                if (asyncVoList.size() == 0){

                    continue;

                }

                List<Object> objectList = asyncVoList.stream().map(AsyncVo::getParams).collect(Collectors.toList());

                if (objectList.size() > 0){

                    List<BatchData> batchDataList = (List)objectList;

                    batchDataService.saveBatch(batchDataList);

                }

            } catch (Exception e) {

                e.printStackTrace();

            }

        }

    }

}

队列监听

@Component

public class QueueListener {

    @Autowired

    private ConsumQuene consumQuene;

    /**

     * 初始化时启动监听请求队列

     */

    @PostConstruct

    public void init() {

        consumQuene.start();

    }

    /**

     * 销毁容器时停止监听任务

     */

    @PreDestroy

    public void destory() {

        consumQuene.setShutDown();

    }

}
  • 3. 压力测试(Jmeter)

每秒启动50个用户线程,持续输出十分钟

3.1 同步模式下使用JMeter压测,结果如下:

 

该模式下,TPS约为: 437/sec

控制台输出如下:

3.2 阻塞队列模式下压测结果如下:

 

该模式下TPS约为: 454/sec  明显高于同步模式下的TPS,并且随着持续加压,区别会越来越明显,这就是相同硬件配置下通过优化代码增加系统吞吐量的关键所在

 

Mysql数据库存储情况

 

 

 

;