背景
实习期间,组长交给我个任务,就是一个压测接口,会有很多用户提交压测任务至该接口,可能会导致同一时间并发处理大量的数据,而我负责的就是优化该接口,使得处理大量数据尽可能的平缓和高效,
于是就想到了用本地队列去优化
队列选择
因为该接口有并发场景的存在,所以我们选择队列时要选择并发线程安全的队列,因此可以找到如下两种队列
- ConcurrentLinkedQueue
- LinkedBlockingQueue
二者区别,
ConcurrentLinkedQueue是非阻塞队列,适用于单线程插入,多线程取出的场景,而LinkedBlockingQueue是阻塞队列,适用于多线程插入,单线程取出,二者更多区别查看spring - LinkedBlockingQueue 和 ConcurrentLinkedQueue的区别 - 个人文章 - SegmentFault 思否
这里我选择的是
LinkedBlockingQueue,因为当前场景是多线程插入和单线程取出
业务伪代码
package org.example.util;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.*;
import lombok.extern.slf4j.Slf4j;
import org.example.pojo.SampleResult;
@Slf4j
public class LocalQueue {
// 本地队列
private final Queue<SampleResult> queue = new LinkedBlockingQueue<>();
// 桶大小
private final int bufferSize;
private final long waitTimeMillis;
// 上次发送时间
private Instant lastSendTime;
public LocalQueue(int bufferSize, long waitTimeMillis) {
this.bufferSize = bufferSize;
this.waitTimeMillis = waitTimeMillis;
this.lastSendTime = Instant.now();
// 启动后台服务
startBackgroundSender();
}
public void receiveSamples(List<SampleResult> samples) {
if (samples != null) {
queue.addAll(samples);
sendDataIfNeeded();
}
}
private void startBackgroundSender() {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
// 后台任务,每隔一段时间执行任务
executor.scheduleAtFixedRate(
this::sendDataIfNeeded, waitTimeMillis, waitTimeMillis, TimeUnit.MILLISECONDS);
}
private void sendDataIfNeeded() {
if (queue.size() >= bufferSize
|| Duration.between(lastSendTime, Instant.now()).toMillis() >= waitTimeMillis) {
List<SampleResult> dataFromQueue = getDataFromQueue(bufferSize);
lastSendTime = Instant.now();
sendDataToServer(dataFromQueue);
}
}
private void sendDataToServer(List<SampleResult> data) {
// 这里实现发送数据到服务端的逻辑
//.......
}
// 获取数据
private List<SampleResult> getDataFromQueue(int size) {
List<SampleResult> data = new ArrayList<>();
while (size > 0 && !queue.isEmpty()) {
data.add(queue.poll());
size--;
}
return data;
}
}
实现细节
主要运用到的是queue和定时任务,其中定时任务的实现采用的线程池提供的
Executors.newScheduledThreadPool(1)创建定时任务线程池服务,再通过该对象创建定时任务 f 而定时任务的具体执行任务内部采用的逻辑是判断此次时间与上次时间相比是否超过了等到时间,而时间类的选择上采用的jdk8新增的Instant时间戳类,通过该对象的Instant.now()可获得当前时间的Instant对象, 通过Duration.between(a,b).toMillis()获得与上次发送时间的差比较判断