共计 2802 个字符,预计需要花费 8 分钟才能阅读完成。
前言
前一段时间,公司共事的一个线上服务 OOM 的问题,我感觉挺有意思的,在这里跟大家一起分享一下。
我过后其实也参加了一部分问题的定位。
1 案发现场
他们有个 mq 消费者服务,在某一天下午,呈现 OOM 了,导致服务间接挂掉。
过后咱们收到了很多内存的报警邮件。
发现问题之后,运维第一工夫,帮他们 dump 了过后的内存快照,以便于开发人员好定位问题。
之后,运维重启了该服务,零碎临时复原了失常。
大家都晓得,如果呈现了线上 OOM 问题,为了不影响用户的失常应用,最快的解决办法就是重启服务。
但重启服务治标不治本,只能长期解决一下问题,如果不找到真正的起因,不免下次在某个不经意的工夫点,又会呈现 OOM 问题。
所以,有必要定位一下具体起因。
2 初步定位问题
过后运维 dump 下来的内存快照文件有 3G 多,太大了,因为公司内网限度,没方法及时给到开发这边。
没方法,只能先从日志文件下手了。
在查日志之前,咱们先查看了 prometheus 上的服务监控。查到了过后那个 mq 消费者服务的内存应用状况,该服务的内存使用率始终都比拟安稳,从 2022-09-26 14:16:29 开始,呈现了一个显著的内存飙升状况。
依据以往经验总结进去的,在追究日志时,工夫点是一个十分重要的过滤条件。
所以,咱们过后重点排查了 2022-09-26 14:16:29 前后 5 秒钟的日志。
因为这个服务,并发量不大,在那段时间的日志量并不多。
所以,咱们很快就锁定了 excel 文件导入导出性能。
该性能的流程图如下:
- 用户通过浏览器上传 excel,调用文件上传接口。
- 该接口会上传 excel 到文件服务器。而后将文件 url,通过 mq 音讯,发送到 mq 服务器。
- mq 消费者生产 mq 音讯,从文件服务器中获取 excel 数据,做业务解决,而后把后果写入新的 excel 中。
- mq 消费者将新 excel 文件上传到文件服务器,而后发 websocket 音讯告诉用户。
- 用户收到告诉后果,而后能够下载新的 excel。
通过日志剖析,工夫点刚好吻合,从 excel 文件导入之后,mq 消费者服务的内存使用率一下子飙升。
3. 打不开 dump 文件
从下面剖析咱们得出初步的论断,线上 mq 消费者服务的 OOM 问题,是因为 excel 导入导出导致的。
于是,咱们查看了相干 excel 文件导入导出代码,并没有发现显著的异样。
为了找到根本原因,咱们不得不把内存快照解析进去。
此时,运维把内存快照曾经想方法发给了相干的开发人员(我的共事)。
那位共事用电脑上装置的内存剖析工具:MAT(Memory Analyzer Tool),筹备关上那个内存快照文件。但因为该文件太大,占了 3G 多的内存,间接关上失败了。
MemoryAnalyzer.ini 文件默认反对关上的内存文件是 1G,起初它将参数 -xmx 批改为 4096m。
批改之后,文件能够关上了,但关上的内容却有问题。
猛然发现,原来是 JDK 版本不匹配导致的。
他用的 MAT 工具是基于 SunJDK,而咱们生成环境用的 OpenJDK,二者有些差别。
SunJDK 采纳 JRL 协定公布,而 OpenJDK 则采纳 GPL V2 协定公布。两个协定尽管都是凋谢源代码的,然而在应用上的不同,GPL V2 容许在商业上应用,而 JRL 只容许集体钻研应用。
所以须要下载一个基于 OpenJDK 版本的 MAT 内存剖析工具。
4. 进一步剖析
刚好,另一个共事的电脑上下载过 OpenJDK 版本的 MAT 内存剖析工具。把文件发给他帮忙剖析了一下。
最初发现 org.apache.poi.xssf.usermodel.XSSFSheet 类的对象占用的内存是最多的。
目前 excel 的导入导出性能,大部分是基于 apache 的 POI 技术,而 POI 给咱们提供了 WorkBook 接口。 罕用的 WorkBook 接口实现有三种:
- HSSFWorkbook:它是晚期应用最多的工具,反对 Excel2003 以前的版本,Excel 的扩展名是.xls。只能导出 65535 条数据,如果超过最大记录条数会报错,但不会呈现内存溢出。
- XSSFWorkbook:它能够操作 Excel2003-Excel2007 之间的版本,Excel 的扩展名是.xlsx。最多能够导出 104w 条数据,会创立大量的对象寄存到内存中,可能会导致内存溢出。
- SXSSFWorkbook:它能够操作 Excel2007 之后的所有版本,Excel 的扩展名是.xlsx。SXSSFWorkbook 是 streaming 版本的 XSSFWorkbook,它只会保留最新的 rows 在内存里供查看,以前的 rows 都会被写入到硬盘里。用磁盘空间换内存空间,不会导致内存溢出。
看到了这个类,能够验证之前咱们通过日志剖析问题,得出 excel 导入导出性能引起 OOM 的论断,是正确的。那个引起 OOM 问题的性能,刚好应用了 XSSFWorkbook 解决 excel,一次性创立了大量的对象。要害代码如下:
XSSFWorkbook wb = new XSSFWorkbook(new FileInputStream(file));
咱们通过 MAT 内存剖析工具,曾经确定 OOM 问题的起因了。接下来,最要害的一点是:如何解决这个问题呢?
5. 如何解决问题?
依据咱们下面的剖析,既然 XSSFWorkbook 在导入导出大 excel 文件时,会导致内存溢出。那么,咱们改成 SXSSFWorkbook 不就行了?要害代码改变如下:
XSSFWorkbook wb = new XSSFWorkbook(new FileInputStream(file));
应用 SXSSFWorkbook 将 XSSFWorkbook 封装了一层,其中 100 示意 excel 一次读入内存的最大记录条数,excel 中其余的数据将会生成临时文件保留到磁盘上。这个参数,能够依据理论须要调整。还有一点十分重要:
sheet.flushRows();
须要在程序的结尾处加上下面的这段代码,不然生成的临时文件是空的。这样调整之后,问题被临时解决了。
此外,顺便说一句,在应用 WorkBook 接口的相干实现类时,用完之后,要记得调用 close 办法及时敞开喔,不然也可能会呈现 OOM 问题。
6. 后续思考
其实,过后我倡议过应用阿里开源的 EasyExcel 解决 OOM 的问题。但共事说,excel 中有很多款式,在导出的新 excel 中要保留之前的款式,同时减少一列,返回导入的后果。
如果应用 EasyExcel 不太好解决,应用原始的 Workbook 更好解决一些。
然而应用 mq 异步导入 excel 文件这套计划,如果并发量大的话,任然可能会呈现 OOM 问题,有安全隐患。因而,有必要调整一下 mq 消费者。
起初,mq 消费者的线程池,设置成 4 个线程生产,防止消费者同时解决过多的音讯,读取大量的 excel,导致内存占用过多的问题。当然线程个数参数,能够依据理论状况调整。
此外,应用阿里的 arthas 也能够定位线上 OOM 问题,前面会有专门的文章介绍,感兴趣的小伙伴能够关注一下。