当前位置: 首页 > 后端技术 > Node.js

Node.js应用故障排查手册—Avalanche内存泄漏问题

时间:2023-04-03 23:26:42 Node.js

摘要:在一些问题场景中,应用的内存泄漏非常严重和迅速,甚至在我们的报警系统察觉之前就导致了应用的OOM。这个时候我们没有时间或者没有办法去获取堆快照,所以也没办法用前面的方法来分析进程为什么会泄漏内存溢出然后崩溃。在WedgePracticePart1中,我们也看到了一个典型的由于开发者使用第三方库不当,在配置信息中携带了第三方库本身不能使用的信息而导致内存泄露的案例。其实类似于这种比较慢的Node.js应用内存泄漏我们总能在合适的时机抓取堆快照进行分析,而堆快照一般是分析内存泄漏的最佳方式。但是,仍然存在一些问题场景,应用的内存泄漏非常严重和迅速,甚至在我们的报警系统察觉不到之前就导致了应用的OOM。这个时候,我们没有时间或者没有办法去获取堆快照,所以我们就没有办法用前面的方法来分析进程为什么会泄漏内存溢出然后崩溃。这种问题场景其实是线上Node.js应用内存问题的一种极端情况。本节还将以实际生产中的案例来说明如何处理这种极端的内存异常。本书首发于Github,仓库地址:https://github.com/aliyun-node/Node.js-Troubleshooting-Guide,云栖社区同步更新。由于例子的特殊性,我们需要先给出你的生产案例的最小化复现代码。建议读者自行运行这段代码,这样后面的排查分析过程结合起来会更有用。报酬。最小可复现代码仍然基于Egg.js,如下:'usestrict';constController=require('egg').Controller;constfs=require('fs');constpath=require('path');constutil=require('util');constreadFile=util.promisify(fs.readFile);classDatabaseErrorextendsError{constructor(message,stack,sql){super();this.name='SequelizeDatabaseError';这个.message=消息;这个。堆栈=堆栈;这个.sql=sql;}}classMemoryControllerextendsController{asyncoom(){const{ctx}=this;让bigErrorMessage=awaitreadFile(path.join(__dirname,'resource/error.txt'));bigErrorMessage=bigErrorMessage.toString();consterror=newDatabaseError(bigErrorMessage,bigErrorMessage,bigErrorMessage);ctx.logger.error(错误);ctx.body={ok:false};}}module.exports=MemoryController;这里我们还需要在app/controller/目录下创建一个resource文件夹,并在该文件夹下添加一个error.txt。这个TXT的内容是随机的,只要是能超过100M的大一串就行了。值得注意的是,这里的问题在egg-logger>=1.7.1版本中已经修复,所以要重现当时的情况,需要在Demo的根目录下执行以下三个命令来恢复当前state版本状态:rm-rfpackage-lock.jsonrm-rfnode_modules/egg/egg-loggernpminstallegg-logger@1.7.0最后使用npmrundev启动demo,尽量减少这个问题的再次发生。在感知过程出现问题的情况下,其实我们线上的Node.js应用差点就触发了这个bug然后内存溢出就直接崩溃??了,而平台预设的内存阈值报警其实就是一个定时上报的逻辑,所以,有一个延迟,在这种情况下,我们无法像冗余配置传输导致的内存溢出问题那样得到Node.js进程级别内存超过预设阈值的告警。那么我们如何看待这里的错误呢?这里我们的服务器已经配置了ulimit-cunlimited,所以当Node.js应用Crash时,内核会自动生成一个coredump文件,性能平台目前支持coredump文件生成警告。您可以参考Node.jsPerformancePlatformUserGuide-ConfiguringAppropriateAlarms工具章节中的部分。详细规则如下:这里需要注意的是coredumpfilealarm服务器需要安装Agenthub/Agentx依赖的Commandx模块版本在1.5.2以上(含).关于这块更详细的信息,也可以参考官方文档CoreDumpAnalysisCapabilities部分。故障处理流程一、分析堆栈信息依赖于上述平台生成coredump文件时的告警。当我们收到告警信息后登录控制台,可以看到新生成的core已经出现在Coredump文件列表中。对于dump文件,继续参考《Node.js性能平台用户指南-CoreDump分析》Tools章节中给出的dump和AliNode自定义分析流程,我们可以看到如下分析结果显示信息:同样,我们直接展开JavaScriptStackinformation检查应用程序崩溃时的堆栈信息:NativeC/C++代码的堆栈信息在屏幕截图中被忽略。其实看JavaScript栈信息就可以得出结论。通过浏览比较有问题的egg-logger@1.7.0中lib/utils.js的代码内容:functionformatError(err){//...//这里调用inspect方法对要序列化的错误对象consterrProperties=Object.keys(err).map(key=>inspect(key,err[key])).join('\n');//...}//inspect方法实际上调用了require('util').inspect来检查错误对象Value是序列化的functioninspect(key,value){return`${key}:${util.inspect(value,{breakLength:Infinity})}`;}所以我们知道在线Node.js应用程序崩溃了此刻正在使用require('util').inspect序列化一个字符串。二。可疑字符串那么这个序列化动作是导致进程崩溃的罪魁祸首吗?我们点击inspect函数的参数,展开查看可疑字符串的详细信息,如下图:点击红框内的参数,可以得到该字符串详情页的链接,如下图下图:再次点击这里的detail链接,在弹出的新页面中可以看到关于这个可疑字符串的所有信息:当时造成线上Node.js应用的堆内存雪崩,几乎瞬间内存溢出,导致崩溃。值得一提的是,我们还可以点击上图中的+号,在当前页面显示更多的题串内容:我们也可以点击页面的一键导出按钮,下载完整的题串:毕竟,对于这样的问题,如果能抓住导致问题的罪魁祸首参数,至少可以更方便地在本地复现。三、修复问题既然我们知道了原因,修复这个问题就相对简单了。egg-logger官方使用circular-json替代了原有的util.inspect序列化动作,并将序列化后的字符串增加到最大只能10000个字符的限制,解决了egg-logger模块中此类包含大字符串的错误对象的序列化问题.在本节的最后,我们将向您展示在线Node.js应用中即时Crash问题的排查思路。最小化复现Demo对应的真实线上故障中,拼接的SQL语句其实非常多。大小在120M左右,所以首先是数据库操作失败,然后数据库操作失败后输出的DatabaseError对象实例将问题SQL语句原封不动的设置为属性,导致ctx.logger.error(error)时间堆的雪崩记忆。借助Node.js性能平台提供的CoreDumpAlarm+OnlineAnalysisCapabilities,无法获取常规CPUProfiles、heapsnapshots等信息的进程莫名崩溃问题变得有迹可循。事实上,它作为一种自下而上的分析手段,极大地提高了开发者真正将Node.js应用到服务端生产环境中的信心。本文作者:易君阅读原文,为云栖社区原创内容,未经允许不得转载。