与正常的导出功能不同,应局方要求需要导出一些截至当前的工程采集状态、提交审核状态等字段。该一类字段需要在导出时进行实时统计并且比较耗时。
1. 前期统计方式:单线程全量统计并返回前端
问题:根据当时统计,2000条数据就会超过1min,并且当时没有做数据库调优操作,随着交维系统同步过来的项目数量越来越多,造成
了导出超时的问题
2. 优化
2.1 数据库调优
详见:MySQL数据库优化_Lionel_SSL的博客-CSDN博客_mysql 数据库优化
2.2 导出方式调优
1.改变实时导出并实时返回给前端的方式。采用“任务队列”的形式来实现导出,即用户在前端点击【导出】后,系统提示【任务已加入任务队列,请稍后刷新页面查看】,此时用户可去其他页面进行操作。待后端导出完成后会给出下载连接,用户点击链接下载文件。
(1)前端:提供文件下载列表,实时展示用户的文件导出状态,导出完成后可点击下载
(2)后端:导出任务队列表 + 定时扫描 + 多线程
导出任务队列表:用户点击导出后,会将用户的导出任务入表,并标识”未开始“
定时扫描:利用Spring Boot提供的注解@Scheduled来开启一个定时任务,每分钟都去扫描任务表,是否有未开始导出的任务,有则进行导出
解决@Scheduled()注解造成的多任务冲突问题解决: 关于使用@Scheduled()注解造成的多任务冲突问题解决_Lionel_SSL的博客-CSDN博客
多线程:将所有的数据交给不同的线程来进行导出,最后再将结果汇总
Java 1.5之后提供了Callable和Future接口,通过它们就可以在任务执行完毕之后得到任务的执行结果
// 多线程处理导出时的数据查询
int threadNum = 4;
int singleSize = idList.size() / threadNum;// 一个线程处理的数据
// 创建线程池,拥有固定数量(此处可继续优化,按照阿里规约,线程池应自己定义)
ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
List<Callable<List<ProjectDetailOutBO>>> tasks = new ArrayList<>();
Callable<List<ProjectDetailOutBO>> task = null;
// 设置每个任务
for (int i = 0; i < 4; i++) {
int loop = i;
task = () -> {
// 分割数组
List<BigDecimal> subIdList;
if (loop == 0) {
subIdList = idList.subList(0, singleSize);
} else if (loop == 3) {
subIdList = idList.subList(singleSize * 3, idList.size());
} else {
subIdList = idList.subList(singleSize * loop, singleSize * (loop + 1));
}
// 定义接收每个线程返回的业务结果集
List<ProjectDetailOutBO> resList = new ArrayList<>();
for (BigDecimal id : subIdList) {
ProjectDetailOutBO tmpBO = getProjectDetail(id);
resList.add(tmpBO);
}
return resList;
};
// 添加任务
tasks.add(task);
}
try {
// 执行所有的任务并获取结果
List<Future<List<ProjectDetailOutBO>>> futures = executorService.invokeAll(tasks);
for (Future<List<ProjectDetailOutBO>> future : futures) {
List<ProjectDetailOutBO> detail = future.get();
details.addAll(detail);
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
// 关闭线程池
executorService.shutdown();