背景
物资系统”物资库存”导出功能,导出的数据几千条上万条,耗时非常长,经分析,导出前端主导,前端一页一页地获取,全部获取完成后,前端生成xlsx文件。
设计有两个问题,1, 分页大小100,几千条数据前后端来回几十次;2,数据全部放在浏览器,用户内存使用造成压力
本文介绍大数据量处理性能优化方案,引入进程内的并行执行组件,多线程分片并行执行,支持数据量少降级到单线程,减少消耗;支持进度反馈;使用磁盘文件,支持百万数据导出
本组件是进程内的分片并行,若分布式的并行执行,支持几十上百节点大规模计算,需引入zookeeper
并行执行组件
组件使用ExecutorService和CountDownLatch,实现多线程并行,支持平均分片策略,支持进度实时反馈
开发示例
本节以物资系统的”物资库存”为例,介绍并行组件开发和使用,整个过程分3个阶段,
0 准备
- 首先设置线程数量,实际根据数据量和系统负载计算合适的线程数
- 生成uuid,返回给前端,后续前端带上uuid查询进度,下载文件
- 执行状态缓存,两种方式,本地map;分布式redis,前者在集群环境要考虑session亲和,uuid哈希,但健壮性不好,如,节点下线或者增加节点都会出问题,推荐使用redis,更新进度closure需要原子操作,可以使用lua或者getAndSet的原子操作,保证进度在多线程下正确更新
上图准备写入文档
1 导出任务
上图构建进度闭包类,执行中任务的线程各自累加完成量,计算出进度
上图是分片执行体,整个需要使用executor异步执行,因为ShardingExecutor使用LatchCountDown阻塞等待所有执行线程。
构建和初始化执行器,安全上下文,业务处理可能需要用到用户信息,权限信息等,手动传递上下文,如果使用了inherited线程本地变量可自动传递
业务逻辑实现为Consumer,接收分片,即,total根据平均分片,业务方法getPage自行解释,可以是0~n,也可以是a-c等等
数据处理完毕,进度inc更新进度,方法需要线程安全,可以使用AtomicLong之类的保证
上图是进度闭包的简单实现
2 定时获取进度;
前端打开任务后,带上uuid,定时调用获取进度
3 下载
客户端使用UUID下载文件
X 定时清理任务
服务端实现定时清理任务,根据任务超时时间和进度清理,这是全局的,若不需支持进度反馈,合并上面1,2,3为一个方法,并在最后清理,不需要全局的清理任务。
Benchmark
模拟服务端导出列表,包括写入excel文件,数据量使用过滤ISSUEUNIT字段控制
数据1000,线程数 1,3,10
数据数/线程数 | 1 | 3 | 10 |
1267 | 1.77/1.85/1.75 | 1.80/1.70/1.68 | 1.77/1.69 |
*以上统计连续跑3次,小数点后第三位不为0进一,单位:秒,下同
数据4000,线程数 1,3,5,10
数据数/线程数 | 1 | 3 | 5 | 10 |
4000 | 4.23/3.53/3.46 | 2.67/2.40/2.33 | 2.24/2.15/2.03 | 2.76/2.10/2.36 |
数据20000,线程数 5,10,20
数据数/线程数 | 5 | 10 | 20 |
24168 | 5.70/5.46/5.47 | 4.45/4.63/4.53 | 4.88/4.21/4.41 |
数据40000,线程数 5,10,20,30
数据数/线程数 | 5 | 10 | 20 | 30 |
42710 | 11.95/10.84/10.37 | 7.39/7.38 | 6.93/6.69 | 6.30/6.25 |
以上测试在IDE执行,jar包jdk服务模式运行性能会好些
总结:数据量越大,线程数多性能好,反之,数据量少,线程少性能好或者区别不大
NEXT
优化:可重置可复用的线程池,降低线程构建和销毁的消耗