高性能批处理代码
当数据量达到一定数值或者时间达到一定间隔,任何一个满足条件,都将触发任务,任务完成之后再进行下一次条件判断,(能满足海量数据的批量传输和少量数据的较低延迟传输)
条件1: 当时间达到了一定间隔 称为时间触发(自定义名称,方便理解后续代码)
条件2: 当条数达到了一定数量,称为条数触发(自定义名称,方便理解后续代码)
每一次触发计算后都会清空条件1的计时(重新计时),同时也会清空条件2的数量(重新计数)
核心思路
1.每次添加数据都判断一次是否达到了批处理的最大数据量
2.开启一个循环定时任务 每次到达规定时间间隔(时间触发)就检查是否需要批处理(两次时间触发之间没有出现条数触发就需要执行时间触发)
3.在执行批处理的时候 不允许添加新的数据,所以在执行批处理方法与添加数据的方法中要添加相同的当前对象锁this
代码如下
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import java.util.function.Consumer;
/**
* 批处理类
* 时间间隔或数据量两个条件满足任何一个即可触发任务
* 往对象中添加数据,达到上面所述的任何一个条件时,会触发任务,之后自动重置条件并清空缓存添加的数据
*/
@Slf4j
@Data
public class BatchCommon<T> {
private List<T> dataList;//存储添加的数据
private int maxCount;//最大批处理数据条数
private String jobName;//任务名称
private long waitMilliSecond;//最大等待时间
private Consumer<List<T>> consumer;//自定义函数 内部用来处理批次的数据
private boolean lastJobIsMaxCount;//上一次是否是时间触发
private boolean closeTimeJob;//是否需要关闭时间触发任务
private long sum = 0;//累计处理数据量
private final Object obj = new Object();//累计处理数据量
/**
* 默认初始化
*
* @param jobName 任务名称
* @param maxCount 最大批处理数据条数
* @param waitMilliSecond 时间触发间隔 毫秒
* @param consumer 自定义处理方法
*/
public BatchCommon(String jobName, int maxCount, long waitMilliSecond, Consumer<List<T>> consumer) {
this.jobName = jobName;
this.maxCount = maxCount;
this.dataList = new ArrayList<>(maxCount);
this.waitMilliSecond = waitMilliSecond;
this.consumer = consumer;
this.lastJobIsMaxCount = true;
this.closeTimeJob = false;
getTimeJob().start();//开启时间触发任务
}
/**
* 时间触发任务
*
* @return 时间触发任务对象
*/
public Thread getTimeJob() {
return new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
while (true) {
Thread.sleep(waitMilliSecond);//休眠
synchronized (obj) {
if (closeTimeJob && dataList.isEmpty()) {
log.info("退出: {}, 累计处理数据量: {}", jobName, sum);
break;
}
if (dataList.isEmpty()) continue;
if (lastJobIsMaxCount) {//只有中途没有出现条数触发,则执行时间触发,如果中途出现了一次条数触发,跳过当前的时间触发
exec("时间触发");
} else {
lastJobIsMaxCount = true;
}
}
}
}
});
}
/**
* 添加数据
*
* @param data 被添加的数据
*/
public void add(T data) {
synchronized (obj) {
dataList.add(data);
if (dataList.size() == this.maxCount) {
lastJobIsMaxCount = false;//置换为条数触发
exec("数量触发");
}
}
}
/**
* 执行批处理任务
*
* @param execType 批处理的触发方式
*/
@SneakyThrows
private void exec(String execType) {
log.info("任务: {}, 类型: {} 数据量: {}", jobName, execType, dataList.size());
consumer.accept(dataList);
sum += dataList.size();
dataList.clear();
}
/**
* 关闭任务,需要安全关闭 所以这里设置的是状态,在线程里面检查状态后主动退出
*/
public void close() {
synchronized (this) {
closeTimeJob = true;
}
}
public static void main(String[] args) throws InterruptedException {
Consumer<List<String>> consumer = list -> {
System.out.println("处理当前一批数据的方法体" + list.size());
};
BatchCommon<String> batchCommon1 = new BatchCommon<>("任务1", 3, 5000, consumer);
BatchCommon<String> batchCommon2 = new BatchCommon<>("任务2", 3, 5000, consumer);
BatchCommon<String> batchCommon3 = new BatchCommon<>("任务3", 3, 5000, consumer);
for (int i = 0; i < 200; i++) {
new Thread(() -> batchCommon1.add("")).start();
new Thread(() -> batchCommon2.add("")).start();
new Thread(() -> batchCommon3.add("")).start();
Thread.sleep(Math.round(Math.random() * 200));
}
batchCommon1.close();//执行完成后,退出任务 需要关闭时间触发的循环任务
batchCommon2.close();
batchCommon3.close();
}
}