高性能批处理代码
当数据量达到一定数值或者时间达到一定间隔,任何一个满足条件,都将触发任务,任务完成之后再进行下一次条件判断,(能满足海量数据的批量传输和少量数据的较低延迟传输)
条件1: 当时间达到了一定间隔 称为时间触发(自定义名称,方便理解后续代码)
条件2: 当条数达到了一定数量,称为条数触发(自定义名称,方便理解后续代码)
每一次触发计算后都会清空条件1的计时(重新计时),同时也会清空条件2的数量(重新计数)
重新计时有两种方式(1.强制精确计时,2.有一定误差计时,最大时间误差为一个时间触发的间隔)
方式1: 强制精确计时
时间间隔的触发,一定是间隔指定时间而且指定间隔之间没有条数触发,每一次条数触发,一定是达到了最大条数, 对于时间触发,需要开启一个休眠时间任务,条数达到之后stop休眠任务,需要强制停止休眠的线程(强制停止线程可能会造成应该回收的连接没有回收,一些清理性的工作得不到完成),条数未达到,休眠结束,执行休眠后的任务处理代码,代码如下
代码如下:
import org.apache.log4j.Logger;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.function.Function;
/**
*
*/
public class BatchExec<T> {
private Logger logger = Logger.getLogger(this.getClass());
public int maxCount;//批处理触发最大条数
public int waitMilliSecond;//批处理触发最大等待时间
public List<T> dataList;//批处理缓存数据
public Thread timeJob;//时间触发任务
public Function<List<T>, Object> function;//jdk1.8中的函数式编程,将方法当成参数
public SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");//时间格式化
/**
* 默认初始化
*/
public BatchExec() {
this.dataList = new ArrayList<>(100);//保存数据使用
this.function = result -> {
logger.info(String.format("触发默认方法,数据长度:%s", result.size()));
return result;
};
this.maxCount = 100;
this.waitMilliSecond = 3000;
this.timeJob = this.initThread();
this.timeJob.start();
}
/**
* 指定参数初始化
*
* @param maxCount
* @param waitMilliSecond
*/
public BatchExec(int maxCount, int waitMilliSecond, Function<List<T>, Object> function) {
this.dataList = new ArrayList<>(maxCount);//使用连表
this.function = function;
this.maxCount = maxCount;
this.waitMilliSecond = waitMilliSecond;
this.timeJob = this.initThread();
this.timeJob.start();
}
/**
* 重置时间间隔任务任务
*
* @return
*/
public Thread initThread() {
Thread job = new Thread(() -> {//定义
try {
Thread.sleep(waitMilliSecond);
waitMilliSecond();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
return job;
}
/**
* 往集合中添加需要处理的数据
*
* @param data
*/
public synchronized void add(T data) {
this.dataList.add(data);
if (this.dataList.size() == this.maxCount) {//触发
timeJob.stop();
exec("maxCount");
//重置时间与数量
this.timeJob = this.initThread();
this.timeJob.start();
}
}
/**
* 达到一定时间触发任务
* 达到时间执行与达到数量执行两个方法需要加锁,即waitMilliSecond 与 add
*/
public synchronized void waitMilliSecond() {
if (this.dataList.size() != 0) {//空数据
exec("timeType");
}
//重置时间任务
this.timeJob = this.initThread();
this.timeJob.start();
}
/**
* 任务执行并清空已经处理的数据
*
* @param type
*/
public void exec(String type) {
logger.debug(String.format("time :[%s], trigger type [%s]", getNowFormatDay(), type));
function.apply(this.dataList);
dataList.clear();//重置数据量
}
/**
* 格式化获取当前时间
*
* @return
*/
public String getNowFormatDay() {
String dateString;
Calendar cal = Calendar.getInstance();
dateString = this.sdf.format(cal.getTime());
return dateString;
}
}
方式2: 有一定误差计时
时间间隔的触发,并非严格的时间间隔,即时间间隔触发是在一到两个间隔之间肯定触发,每一次条数触发,一定是达到了最大条数, 对于非严格时间触发,需要开启一个单线程的循环时间休眠任务,休眠任务会判断两次休眠之间之间是否有条数达到的情况如果达到,不需要强制停止休眠的线程,只需要加一个if判断代码中记录两次休眠之间是否有数量触发的情况,如果有,即跳过任务处理逻辑,否则任务处理,代码如下
import org.apache.log4j.Logger;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.function.Consumer;
/**
* 批处理类
* 时间间隔或数据量两个条件满足触发任务
* 暂时没有用上这个类
*/
public class BatchCommon<T> {
private Logger logger = Logger.getLogger(this.getClass());
public int maxCount;//批处理触发最大条数
public int waitMilliSecond;//批处理触发最大等待时间
public List<T> dataList;//批处理缓存数据,有锁
public Thread timeJob;//时间触发任务
public Consumer<List<T>> function;//函数式编程,将方法当成参数,这个函数可以不用返回值,Function必须有返回值
public SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");//时间格式化
public boolean status = true;//两次休眠之间是否有达到条数触发,修改变量有锁,一定误差计时的误差范围是[0,最大等待时间]
/**
* 默认初始化
*/
public BatchCommon() {
this.dataList = new ArrayList<>(100);//保存数据使用
this.function = result -> {
logger.info(String.format("触发默认方法,数据长度:%s", result.size()));
};
this.maxCount = 100;
this.waitMilliSecond = 3000;
this.timeJob = this.initThread();
this.timeJob.start();
}
/**
* 指定参数初始化
*
* @param maxCount
* @param waitMilliSecond
*/
public BatchCommon(int maxCount, int waitMilliSecond, Consumer<List<T>> function) {
this.dataList = new ArrayList<>(maxCount);//使用数组
this.function = function;
this.maxCount = maxCount;
this.waitMilliSecond = waitMilliSecond;
this.timeJob = this.initThread();
this.timeJob.start();
}
/**
* 重置时间间隔任务任务
*
* @return
*/
public Thread initThread() {
Thread job = new Thread(() -> {//定义
try {
while (true) {
Thread.sleep(waitMilliSecond);
if (this.isTimeExec()) waitMilliSecond();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
return job;
}
/**
* 是否需要触发时间执行
*
* @return
*/
public synchronized boolean isTimeExec() {
if (this.status) {
this.status = false;//更新当前时间时间检查没有出现条数触发
return false;//上一个时间检查存在条数触发,不可以更新任务
} else {
return true;//没有条数触发,可以执行任务
}
}
/**
* 往集合中添加需要处理的数据
*
* @param data
*/
public synchronized void add(T data) {
this.dataList.add(data);
if (this.dataList.size() == this.maxCount) {//触发
this.status = true;//记录已经发生条数触发
exec("maxCount");//条数触发
}
}
/**
* 达到一定时间触发任务
* 达到时间执行与达到数量执行两个方法需要加锁,即waitMilliSecond 与 add
*/
public synchronized void waitMilliSecond() {
if (this.dataList.size() != 0) {//空数据
exec("timeType");//时间触发
}
}
/**
* 任务执行并清空已经处理的数据
*
* @param type 触发任务类型
*/
public void exec(String type) {
logger.warn(String.format("time :[%s], trigger type [%s]", getNowFormatDay(), type));
function.accept(this.dataList);
dataList.clear();//重置数据量
}
/**
* 格式化获取当前时间
*
* @return
*/
public String getNowFormatDay() {
String dateString;
Calendar cal = Calendar.getInstance();
dateString = this.sdf.format(cal.getTime());
return dateString;
}
}
主方法入口代码:
public static void main(String[] args) throws InterruptedException {
Consumer<List<String>> consumer = list -> {
System.out.println(list.size());
};
BatchCommon<String> stringBatchCommon = new BatchCommon<>(2, 1000, consumer);
while (true) {
Thread.sleep(1000);
stringBatchCommon.add("");
}
}