背景:
订单导出功能,因公司业务需要,订单导出的字段,需要关联15张表。
优化历程:
问题1:一开始用poi,出现过线上内存溢出的问题,
解决问题1:后通过加大内存,改用占用内存量小的工具easypoi,后来没出现过内存溢出问题。
问题2.:导出一个3000条数据的excel,经常超时。导致excel无法正常下载。
解决问题 2:原因是导出耗时比较久,出现超时的情况,这个时候把导出功能 迁移出去,在一个单独的 项目中去处理,处理完后,将生成的excel文件放到七牛云,让用户再去下载。
问题3:导出功能迁移到单独项目后,导出一个3000条数据的excel,居然需要30分钟!!!!
这里就详细说明目前处理方案和思路:
导出excel的字段,需要关联15张表才能完成产品的需求。
方案1,优化索引
关联15张表,改建的索引都建了,还是慢。优化索引后需要大概15分钟,具体看数据量。
很明显方案1还是解决不了问题,因为还是太慢了。
方案2,改成单表查询,然后聚合订单表的外键字段,再去批量查15张表的数据信息后,再聚合数据
方案2处理结果还是不行,还是慢,跟方案1的耗时差不多。
因为一开始去批量查15张表是串行的,也就是查完第一张表耗时10秒后,才能继续查第二张表,以此类推,所以最后的
耗时其实即使查16次表的时间总和加上数据聚合的时间。
相当于订到导出的耗时=1次订单表查询的时间+15张关联表每个的查询耗时总和+聚合数据的时间
方案3,改成单标查询,然后聚合订单表的外建字段,再用多线程异步批量查15张表的数据信息后,再聚合数据
方案3,暂时提高了效率,导出时间优化到了10分之1
相当于订到导出的耗时=1次订单表查询的时间+15张关联表中耗时最长的那次sql查询时间+聚合数据的时间
基于公司的保密,这里就简单演示下多线程的处理方式。
采用的是CompletableFuture,示例
模拟耗时的数据库查询方法,然后下文分别模拟了7个耗时,等同于去查询15张表外键关联表的操作,分别命名1,2,3,4,5,6,7
private static String doThing1() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("dothing1");
return "dothing1";
}
模拟串行需要的时间
public static void main(String[] args) throws Exception{
long beign = System.currentTimeMillis();
doThing1();
doThing2();
doThing3();
doThing4();
doThing5();
doThing6();
doThing7();
long end = System.currentTimeMillis();
System.out.println("总耗时="+(end-beign));
}
耗时20004毫秒
模拟并行的耗时
public static void main(String[] args) throws Exception{
long beign = System.currentTimeMillis();
//1
CompletableFuture<String> thing1Future = CompletableFuture.supplyAsync(() -> {
return doThing1();
});
//2
CompletableFuture<String> thing2Future = CompletableFuture.supplyAsync(() -> {
return doThing2();
});
//3
CompletableFuture<String> thing3Future = CompletableFuture.supplyAsync(() -> {
return doThing3();
});
//4
CompletableFuture<String> thing4Future = CompletableFuture.supplyAsync(() -> {
return doThing4();
});
//5
CompletableFuture<String> thing5Future = CompletableFuture.supplyAsync(() -> {
return doThing5();
});
//6
CompletableFuture<String> thing6Future = CompletableFuture.supplyAsync(() -> {
return doThing6();
});
//7
CompletableFuture<String> thing7Future = CompletableFuture.supplyAsync(() -> {
return doThing7();
});
//等所有任务完成再执行结果
CompletableFuture.allOf(thing1Future, thing2Future,
thing3Future,thing4Future,
thing5Future,thing6Future,
thing7Future).join();
System.out.println("thing1Future -> " + thing1Future.get());
System.out.println("thing2Future -> " + thing2Future.get());
System.out.println("thing3Future -> " + thing3Future.get());
System.out.println("thing4Future -> " + thing4Future.get());
System.out.println("thing5Future -> " + thing5Future.get());
System.out.println("thing6Future -> " + thing6Future.get());
System.out.println("thing7Future -> " + thing7Future.get());
long end = System.currentTimeMillis();
System.out.println("总耗时="+(end-beign));
}
耗时5043毫秒
现在订单导出的处理方案,暂时就是按这种方式处理,后续如果有问题,应该还需要继续优化方案。
技能有限,如有更好的方案,欢迎文章下方留言讨论