记一次暴力编码导出Excel引发的OOM
记一次暴力编码导出Excel引发的OOM
1、业务背景
现有某线上一对一授课产品的后台 CRM 系统,采用 Spring Cloud 全家桶微服务架构,如图:

2、问题现象
我司业务(后台系统用户)向技术部门反馈,后台系统在平常的使用中,经常会出现操作失败的情况,也不是完全不能用,还是能用。
以上现象,偶尔发生,注意是现象偶尔发生;但是一旦发生,“经常操作失败”这一问题就会必现。
3、排查过程
得知此事,我建议同事(Tips:同事比我入职的早,负责发版的工作,该问题历史以来就有)在下次发版时,给 JVM 配置上如下参数:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/base果不其然,2021 年 8 月 11 日,该问题再次复现,遂即让同事 SSH 到该服务器(从阿里云后台查看得知该节点内存飙高),并成功的把 dump 文件下载了下来,接着用 JProfiler 进行分析。
(1)分析 1(Classes 总览)

(2)分析 2-1(大对象总览)

(3)分析 2-2(大对象详情)

(4)分析 2-3(大对象详情深入)

(5)分析 2-4(大对象详情更深入)

(6)分析 3-1(大对象被引用的地方)

(7)分析 3-2(大对象被引用的地方详情)

可以明显的看出,char[]、String、HashMap 三个类型的实例数量特别大,达到了几千万个!占用的内存达到了 2G(Tips:同事说机器为 16G,MongoDB 也比较吃内存,日常情况下剩余 2G,此次直接把这剩余的 2G 也给占了,OOM)。
并且跟 CustomerServiceImpl.queryExportDate() 方法有关,然后查看了下该方法的逻辑,就是一个根据条件导出所有数据到 Excel 的功能,跟着逻辑仔细走了一遍,一切豁然开朗。
4、定位原因
理论上来讲,导出 Excel 要分批的把数据写入到文件中,此处的方法不是这么实现的,而是直接一股脑查询到所有的数据放在内存中,再写入到文件,况且此处要查的数据维度特别广,可想而知,数据量巨大的情况下,内存是撑不住的。
事后问了问业务部门,他们有人确实做过该操作!
另外,由于是 3 个服务节点,请求会随机落在这个节点上,这也就说明了为什么是“经常”出现这个情况,而不是每步操作都出现。
5、解决方案
方案一:同业务部进行了沟通,该功能存在的意义不大,使用频率不高;因此,暂时下线了该功能。
方案二:优化导出方法逻辑,导出时分批次将数据写入文件。
方案三:执行起来比较困难,添加统计功能,将各种维度的数据记录到统计表中,而无需耗费大量资源去实时计算。
6、总结经验
编码一定要慎重,思考极端情况,控制风险。

