大批量数据导出优化

5 篇文章 0 订阅

问题产生背景

用户操作导出明细账等大批量数据导出时,导致线上OOM频繁发生
通过分析内存快照可以发现,POI的对象以及相关的XML对象占用了绝大部分的内存消耗,由于导出的数据比较大量,后台直接用XSSFWorkbook导出,在导出结束前内存有大量的Row,Cell,Style等,以及基于XLSX底层存储的XML对象没有被释放,频繁GC无果最终导致内存溢出。

原因分析

  1. 从服务本身实现来看
    导出服务占用大量内存通常在这两个阶段:
    1. 首先,通过sql大批量查询,使用对象接收时会创建大量的对象从而占用大量内存资源;
    2. 其次,数据对象转化为POI对象XSSFWorkbook时,创建大量Row\Cell 对象,进一步加大内存压力。
  2. 从用户操作来看
    通常系统在判断可能需要大量数据导出时,通常会创建新的线程来执行异步导出任务,并且立即给予用户异步导出的提示,但用户可能在导出期间重复点击导出按钮,从而创建大量重复的导出任务浪费系统资源。
  3. 不规范的线程池创建
    虽然异步的导出任务使用了线程池,但是大多数情况下没有太多实际意义。线程池虽能提高线程的复用性和限制开辟大量多线程,但是前提应该建立在一组服务的线程出口应该仅使用一个线程池对象,而不应该每一个Service都创建一个线程池,不仅复用性低,并且使用new CachedThreadPool这种方式起不到约束线程总量的作用。

优化思路

  1. 限制导出服务开辟线程总量,通过适当长度的阻塞队列排队导出,来稳定系统压力

    通常用户在进行大批量数据导出时,对导出耗时并不会有苛刻要求(事实证明后边的优化不仅没有增加导出时间,反而大幅提升导出效率)

  2. 自定义分布锁控制,对特定需要限制用户重复操作的接口,通过同步锁来进行限制

  3. 使用高版本 SXSSFWorkbook 替代低版本 XSSFWorkbook

    通过显示指定窗口大小,超出窗口则进行落盘,从而减少内存占用

    HSSFWorkbook、XSSFWorkbook、SXSSFWorkbook的区别:

    HSSFWorkbook一般用于Excel2003版及更早版本(扩展名为.xls)的导出。

    XSSFWorkbook一般用于Excel2007版(扩展名为.xlsx)的导出。SXSSFWorkbook一般用于大数据量的导出。
    注:HSSFWorkbook和XSSFWorkbook的Excel Sheet导出条数上限(<=2003版)是65535行、256列,(>=2007版)是1048576行,16384列,如果数据量超过了此上限,那么可以使用SXSSFWorkbook来导出。实际上上万条数据,甚至上千条数据就可以考虑使用SXSSFWorkbook了。

  4. 对于DB查询步骤,使用 ResultHandler 来实现数据对象的即时处理,可以大幅提升内存使用效率(自测百万数据量的情况下,结合SXSSFWorkbook 在导出过程中仅占用 100M以内的内存)

详细优化步骤

  1. 全局线程池

FinanceGlobalExportThreadPool.java
image.png
通过枚举类来创建单例线程池实例,每一个线程池实例核心线程为8,阻塞队列长度为16,总的最大线程数限制为32
FinanceGlobalExportThreadPoolDecorator.java
image.png
通过简单的包装类持有线程池,对submit过程包装异常,透出友好的任务拒绝信息
BooksExportService.java
image.png
image.png
线程池使用及声明

  1. 通过锁控制单一用户并发操作

ConcurrentLock
在这里插入图片描述

自定义注解,用于标注导出方法
ConcurrentLockAspect
在这里插入图片描述

切面逻辑:对于用户首次操作,使用keyPrefix + 企业ID + 用户ID 为key上锁,在失效时间内,当用户再次操作,会给出请勿频繁操作提示(注意,并没有主动通过remove方法去释放锁,而是采用失效时间,因为切面无法判断异步导出何时完成,所以无法主动去释放锁)
exportSubsidiaryLedgerList3
image.png
在方法上声明@ConcurrentLock 注解

  1. SXSSFWorkbook

对原有 ExcelExportUtil2 的exportToBytes方法中,替换一系列XSSFXXX为SXSSFXXX,并且添加刷盘、适配等操作,并且并没有改动原有的类,新的工具类:SxssExportUtil
仅在明细帐导出中使用新的工具类,预计线上运行无误后,逐步可进行其他导出的替换
image.png
image.png

测试

为了确保线上稳定性,仅对明细帐进行优化
下面是测试环境进行的导出测试,因为测试环境明细帐数据很少,所以将导出过程_creatDataBody_进行循环调用来模拟大批量数据导出,实验结果将证明,数据占用内存一定的情况下,不同POI类所使用的内存量
测试环境jvm配置为 -Xmx512M
image.png

  1. 首先使用旧的ExcelExportUtil2工具类,模拟22w数据量进行导出 (1490 * 150 = 223500 )

image.png
导出任务执行后,不出意外出现了OOM
在这里插入图片描述

  1. 使用新版本API的工具类 SxssExportUtil

直接构建 1490 * 700 约等于 1,040,000 百万数据量进行导出测试
image.png
明细帐导出成功!
在这里插入图片描述

查看文件详情
image.png
数据总量:
image.png
文件大小约33.9M 导出数据量 1043003 条
需要注意的是:EXCEL 2007 单Sheet数据总量限制为 1048576 ,超出此范围需要把剩余数据放到新的Sheet中

结论及建议

结论:
SXSSF是xssf的一个与API兼容的流媒体扩展,在必须生成非常大的电子表格且堆空间有限时使用,通过限制对滑动窗口中的行的访问来实现其低内存占用,而XSSF允许访问文档中的所有行。
SXSSF在使用中可以通过新的SXSSFWorkbook(int windowSize)指定工作簿构建时的窗口大小,默认为100行,即表示当行数达到101时,rownum=0的行被刷新到磁盘并从内存中删除,当rownum达到102时,rownum=1的行被刷新,以此类推。

可以看出,切换为SXSSF可以使导出过程使用较低的内存,但是它也有局限性

  1. 列宽无法根据所有数据行来调整(已使用最低宽度来解决样式问题)

image.png

  1. 注意显示调用workbook.dispose_()_;来清理临时文件
    image.png
  2. 已经刷新到磁盘的行,不能使用getRow获取,也不能使用createRow覆盖,否则会抛出异常,例如,我再去计算报表尾行的行索引时,使用sheet.getLastRowNum()发现获取到的值竟然是0,导致后边在创建行时,因为0行已经落盘,导致异常,于是使用偏移量加数据行数来重新计算尾行的行索引,规避了这个bug。
    在这里插入图片描述

建议:
通过切换底层导出方式,可以极大缓解数据转换POI对象内存占用过高的问题,但是数据查询过程中JAVA ORM对象的内存占用仍然没能解决。
因为旧有的导出实现中,我们将数据和导出过程没有解耦合:必须将所有数据查询之后,再转交给工具类去转换导出,这样就不可避免的申请大内存来存储。
其实对于简单的导出场景:查询数据-> 转换导出 完全可以实现为:建立工作表 -> 查询数据过程中,同时进行Row以及Cell的创建-> 结束导出,使用此种方式,org.apache.ibatis.session.ResultHandler就有了用武之地,通过此接口,可以流式的将数据查询出来,在回调中边查询边处理,每次处理方法结束,便可以释放掉对象便于GC回收,实测 SXSSF + 流失查询,百万数据导出过程中,内存占用仅为100M以内
image.png
image.png
jvm启动内存为128m
image.png
从统计图上可以看到,内存维持在80M左右

参考文档

SXSSF (Streaming Usermodel API)
Excel大批量数据的导入和导出,如何做优化?
POI多线程分表导出百万级大数据量EXCEL导出

  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hongmin.shm

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值