当前位置: 首页 > 后端技术 > Java

哎呀,线上服务OOM

时间:2023-04-01 13:44:36 Java

前言前段时间,公司同事遇到线上服务OOM问题。我觉得很有意思,就在这里分享给大家。我当时其实参与了一部分问题的定位。1在案发现场,他们有一个mq消费服务。某日下午,发生OOM,导致服务直接挂掉。当时,我们收到了很多内存报警邮件。发现问题后,运维第一时间帮他们dump了当时的内存快照,方便开发者定位问题。之后运维重启了服务,系统暂时恢复正常。大家都知道,如果线上出现OOM问题,为了不影响用户的正常使用,最快的解决办法就是重启服务。但重启服务只能治标不治本。只能暂时解决问题。如果没有找到真正的原因,下次某个不经意的时间点再次出现OOM问题是难免的。因此,需要定位具体原因。2问题的初步定位当时运维dump的内存快照文件有3G多,太大了。由于公司内网的限制,没办法及时发给开发方。没办法,只能从日志文件入手。在查看日志之前,我们先查看prometheus上的服务监控。找到了当时mq消费服务的内存占用情况。服务的内存占用一直比较稳定。从2022-09-2614:16:29开始,出现了明显的内存暴涨。根据以往的经验,在跟踪日志时,时间点是一个非常重要的过滤条件。因此,我们重点检查了2022-09-2614:16:29前后5秒的日志。由于这个服务,并发量不大,那段时间的日志量也不多。于是,我们很快锁定了excel文件导入导出功能。该功能流程图如下:用户通过浏览器上传excel,调用文件上传接口。该接口将excel上传到文件服务器。然后通过mq消息将文件url发送到mq服务器。mq消费者消费mq消息,从文件服务器获取excel数据,进行业务处理,然后将结果写入新的excel。mq消费者将新的excel文件上传到文件服务器,然后发送websocket消息通知用户。用户会收到结果通知,然后可以下载新的Excel。经过日志分析,时间点刚好吻合。从excel文件导入后,mq消费服务内存占用猛增。3、dump文件打不开通过以上分析,我们可以初步得出结论,在线mq消费服务的OOM问题是由excel的导入导出引起的。因此,我们检查了相关excel文件的导入导出代码,并没有发现明显的异常。为了找到根本原因,我们必须解析出内存快照。此时运维已经设法将内存快照发送给相关开发人员(我的同事)。同事使用了电脑上安装的内存分析工具:MAT(MemoryAnalyzerTool),准备打开内存快照文件。但是由于文件太大,占用内存超过3G,直接打开失败。MemoryAnalyzer.ini文件默认支持打开1G内存文件,后来修改参数-xmx为4096m。修改后文件可以打开,但是打开的内容有问题。突然发现是JDK版本不匹配导致的。他使用的MAT工具是基于SunJDK的,和我们生产环境使用的OpenJDK有一些区别。SunJDK是在JRL协议下发布的,而OpenJDK是在GPLV2协议下发布的。虽然这两个协议都是开源的,但是它们在使用上是不同的。GPLV2允许商业使用,而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是XSSFWorkbook的流式版本。它只将最新的行保存在内存中以供查看,之前的行将写入硬盘。将磁盘空间用于内存空间不会造成内存溢出。看到这个类后,可以验证我们之前通过日志分析过问题,得出excel的导入导出功能导致OOM的结论,是正确的。导致OOM问题的函数只是使用了XSSFWorkbook处理excel,一次性创建了大量的对象。关键代码如下:XSSFWorkbookwb=newXSSFWorkbook(newFileInputStream(file));我们通过MAT内存分析工具确定了OOM问题的原因。接下来,最关键的一点是:如何解决这个问题?5.如何解决问题?根据我们上面的分析,由于XSSFWorkbook在导入导出大的excel文件时会导致内存溢出。那么,我们不能将其更改为SXSSFWorkbook吗?关键代码改动如下:XSSFWorkbookwb=newXSSFWorkbook(newFileInputStream(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问题。后面会有专门的文章介绍,感兴趣的朋友可以关注一下。