前言

前一段时间,公司共事的一个线上服务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文件导入导出性能。

该性能的流程图如下:

  1. 用户通过浏览器上传excel,调用文件上传接口。
  2. 该接口会上传excel到文件服务器。而后将文件url,通过mq音讯,发送到mq服务器。
  3. mq消费者生产mq音讯,从文件服务器中获取excel数据,做业务解决,而后把后果写入新的excel中。
  4. mq消费者将新excel文件上传到文件服务器,而后发websocket音讯告诉用户。
  5. 用户收到告诉后果,而后能够下载新的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问题,前面会有专门的文章介绍,感兴趣的小伙伴能够关注一下。