在优化接口速度过程中,有时避免不了SQL执行速度时间稍微慢的情况,而且如果存在多个SQL查询,整个接口执行的时间必然是这些查询时间之和+代码执行的时间;众所周知SQL查询的时间是比较长的,代码执行的速度还是非常快,本文章分享一个通过使用多线程并行的方式来优化接口速度。
这里有两段SQL查询
....
long start = System.currentTimeMillis();
// SQL查询1
List<Record> courseList = Db.find(Db.getSqlPara(sqlKey + "queryTeachingCourse", Kv.by("globalID", globalId + "%")));
long end = System.currentTimeMillis();
DSFLog.info(StrKit.format("查询结束,消耗时间:{}ms",end-start));
start = System.currentTimeMillis();
// SQL查询2
List<Record> teacherList = Db.find(Db.getSqlPara(sqlKey + "queryteachingcourseteacher", Kv.by("globalID", globalId + "%")));
end = System.currentTimeMillis();
DSFLog.info(StrKit.format("查询结束,消耗时间:{}ms",end-start));
...
以上的两段查询执行顺序是先执行完①后再执行②,总查询时间 t = t1 + t2
从日志和浏览器控制台看得出此时的接口反应速度是非常慢的。
我们开始使用并行优化,对代码进行重构
① 申请3个线程 (最好是大于任务数一到两个,我这里是2个任务)
long start = System.currentTimeMillis();
// 申请3个线程 (最好是大于任务数一到两个,我这里是两个任务)
ExecutorService executor = Executors.newFixedThreadPool(3);
CompletionService<TaskDto> executorCompletionService = new ExecutorCompletionService<TaskDto>(executor);
② TaskDto实体类
@Data
public class TaskDto {
/**
* 区分是哪个任务的唯一标记
*/
private String key;
/**
* 任务需要返回的数据
*/
private List<Record> data;
}
③ 创建并提交任务
// 并行任务①
Callable<TaskDto> courseListCallableTask = () -> {
TaskDto courseListTaskDto = new TaskDto();
List<Record> courseList = Db.find(Db.getSqlPara(sqlKey + "queryTeachingCourse", Kv.by("globalID", globalId + "%")));
// 设置任务①的标识
courseListTaskDto.setKey("courseList");
courseListTaskDto.setData(courseList);
return courseListTaskDto;
};
// 并行任务②
Callable<TaskDto> teacherListCallableTask = () -> {
TaskDto teacherListTaskDto = new TaskDto();
List<Record> teacherList = Db.find(Db.getSqlPara(sqlKey + "queryteachingcourseteacher", Kv.by("globalID", globalId + "%")));
// 设置任务②的标识
teacherListTaskDto.setKey("teacherList");
teacherListTaskDto.setData(teacherList);
return teacherListTaskDto;
};
//提交任务
executorCompletionService.submit(courseListCallableTask);
executorCompletionService.submit(teacherListCallableTask);
④ 取出任务执行结果
List<Record> courseList = new LinkedList<>();
List<Record> teacherList = new LinkedList<>();
try{
// 有几个任务这里循环几次 得到每个任务的执行结果
for (int i = 0; i < 2 ; i++) {
// 这里的超时时间不要设置太长也不要太短,太短会导致任务没执行完就被取出结果导出抛空异常
Future<TaskDto> poll = executorCompletionService.poll(1000, TimeUnit.MILLISECONDS);
TaskDto taskDto = poll.get();
// 取出唯一标识 将任务对应的结果装好
if ("courseList".equals(taskDto.getKey())){
courseList = taskDto.getData();
}else{
teacherList = taskDto.getData();
}
}
}catch (Exception e){
// catch里写一份串行执行的代码 以防止并行执行异常导致整个接口报错
DSFLog.error("并行查询异常,即将改为串行查询:"+e.getMessage());
courseList = Db.find(Db.getSqlPara(sqlKey + "queryTeachingCourse", Kv.by("globalID", globalId + "%")));
teacherList = Db.find(Db.getSqlPara(sqlKey + "queryteachingcourseteacher", Kv.by("globalID", globalId + "%")));
}
long end = System.currentTimeMillis();
DSFLog.info(StrKit.format("查询结束,消耗时间:{}ms",end-start));
优化结束!通过日志看一下代码执行情况
很明显,查询速度得到了很大提示,总查询时间 t = max(t1,t2)
在有更多个任务查询时,总查询时间也只是会是某个任务最大的查询时间t = max(t1,t2,t3,...),而不是所有任务查询时间之和t = t1+t2+t3+t...,从此看出任务数越多接口执行速度提升越大!
当然并行方法优化也有个场景无法使用,也就是查询①和查询②有关联的场景,比如需要拿查询①的结果作为参数才能进行查询②,从执行顺序的角度来看查询①和查询②就只能是串行,无法使用并行。