楔子

实践篇一中我们也看到了一个比较典型的由于开发者不当使用第三方库,而且在配置信息中携带了三方库本身使用不到的信息,导致了内存泄漏的案例,实际上类似这种相对缓慢的 Node.js 应用内存泄漏问题我们总是可以在合适的机会抓取堆快照进行分析,而且堆快照一般来说确实是分析内存泄漏问题的最佳手段。

但是还有一些问题场景下下应用的内存泄漏非常严重和迅速,甚至于在我们的告警系统感知之前就已经造成应用的 OOM 了,这时我们来不及或者说根本没办法获取到堆快照,因此就没有办法借助于之前的办法来分析为什么进程会内存泄漏到溢出进而 Crash 的原因了。这种问题场景实际上属于线上 Node.js 应用内存问题的一个极端状况,本节将同样从源自真实生产的一个案例来来给大家讲解下如何处理这类极端内存异常。

本书首发在 Github,仓库地址:https://github.com/aliyun-node/Node.js-Troubleshooting-Guide,云栖社区会同步更新。

最小化复现代码

同样我们因为例子的特殊性,我们需要首先给出到大家生产案例的最小化复现代码,建议读者自行运行一番此代码,这样结合起来看下面的排查分析过程会更有收获。最小复现代码还是基于 Egg.js,如下所示:

'use strict';
const Controller = require('egg').Controller;
const fs = require('fs');
const path = require('path');
const util = require('util');
const readFile = util.promisify(fs.readFile);class DatabaseError extends Error {constructor(message, stack, sql) {super();this.name = 'SequelizeDatabaseError';this.message = message;this.stack = stack;this.sql = sql;}
}class MemoryController extends Controller {async oom() {const { ctx } = this;let bigErrorMessage = await readFile(path.join(__dirname, 'resource/error.txt'));bigErrorMessage = bigErrorMessage.toString();const error = new DatabaseError(bigErrorMessage, bigErrorMessage, bigErrorMessage);ctx.logger.error(error);ctx.body = { ok: false };}
}module.exports = MemoryController;

这里我们还需要在 app/controller/ 目录下创建一个 resource 文件夹,并且在这个文件夹中添加一个 error.txt,这个 TXT 内容随意,只要是一个能超过 100M 的很大的字符串即可。

值得注意的是,其实这里的问题已经在 egg-logger >= 1.7.1 的版本中修复了,所以要复现当时的状况,你还需要在 Demo 的根目录执行以下的三条命令以恢复当时的版本状况:

rm -rf package-lock.json
rm -rf node_modules/egg/egg-logger
npm install egg-logger@1.7.0

最后使用 npm run dev 启动这个问题最小化复现的 Demo。

感知进程出现问题

这个案例下,实际上我们的线上 Node.js 应用几乎是触发了这个 Bug 后瞬间内存溢出然后 Crash 的,而平台预设的内存阈值告警,实际上是由一个定时上报的逻辑构成,因此存在延时,也导致了这个案例下我们无法像 冗余配置传递引发的内存溢出 问题那样获取到 Node.js 进程级别的内存超过预设阈值的告警。

那么我们如何来感知到这里的错误的呢?这里我们的服务器配置过了 ulimit -c unlimited ,因此 Node.js 应用 Crash 的时候内核会自动生成核心转储文件,而且性能平台目前也支持核心转储文件的生成预警,这一条规则目前也被放入了预设的快速添加告警规则中,可以参考工具篇中 Node.js 性能平台使用指南 - 配置合适的告警 一节,详细的规则内容如下所示:

这里需要注意的是,核心转储文件告警需要我们在服务器上安装的 Agenthub/Agentx 依赖的 Commandx 模块的版本在 1.5.2 之上(包含),这一块更详细的信息也可以看官方文档 核心转储分析能力 一节。

问题排查过程

I. 分析栈信息

依靠上面提到的平台提供的核心转储文件生成时给出的告警,我们在收到报警短信时登录控制台,可以看到 Coredump 文件列表出现了新生成的核心转储文件,继续参照工具篇中 Node.js 性能平台使用指南 - 核心转储分析 中给出的转储和 AliNode 定制分析的过程,我们可以看到如下的分析结果展示信息:

同样我们直接展开 JavaScript 栈信息查看应用 Crash 那一刻的栈信息:

截图中忽略掉了 Native C/C++ 代码的栈信息,这里其实仅仅看 JavaScript 栈信息就能得到结论了,通过翻阅比对出问题的 egg-logger@1.7.0 中 lib/utils.js 的代码内容:

function formatError(err) {// ...// 这里对 Error 对象的 key 和 value 调用 inspect 方法进行序列化const errProperties = Object.keys(err).map(key => inspect(key, err[key])).join('\n');// ...
}// inspect 方法实际上是调用 require('util').inspect 来对错误对象的 value 进行序列化
function inspect(key, value) {return `${key}: ${util.inspect(value, { breakLength: Infinity })}`;
}

这样我们就知道了线上 Node.js 应用在 Crash 的那一刻正在使用 require('util').inspect 对某个字符串进行序列化操作。

II. 可疑字符串

那么这个序列化的动作究竟是不是造成进程 Crash 的元凶呢?我们接着来点击 inspect 函数的参数来展开查看这个可疑的字符串的详细信息,如下图所示:

点击红框中的参数,得到字符串的详情页面链接,如下图所示:

再次点击这里的 detail 链接,既可在弹出的新页面中看到这个可疑字符串的全部信息:


这里可以看到,这个正在被 util.inspect 的字符串大小高达 186.94 兆,显然正是在序列化这么大的字符串的时候,造成了线上 Node.js 应用的堆内存雪崩,几乎在瞬间就内存溢出导致 Crash。

值得一提的是,我们还可以点击上图中的 + 号来在当前页面展示更多的问题字符串内容:

也可以在页面上点击 一键导出 按钮下载问题完整的字符串:

毕竟对于这样的问题来说,如果能抓到产生问题的元凶参数,起码能更方便地进行本地复现。

III. 修复问题

那么知道了原因,其实修复此问题就比较简单了,Egg-logger 官方是使用 circular-json 来替换掉原生的 util.inspect 序列化动作,并且增加序列化后的字符串最大只保留 10000 个字符的限制,这样就解决这种包含大字符串的错误对象在 Egg-logger 模块中的序列化问题。

结尾

本节的给大家展现了对于线上 Node.js 应用出现瞬间 Crash 问题时的排查思路,而在最小化复现 Demo 对应的那个真实线上故障中,实际上是拼接的 SQL 语句非常大,大小约为 120M,因此首先导致数据库操作失败,接着数据库操作失败后输出的 DatabaseError 对象实例上则原封不动地将问题 SQL 语句设置到属性上,从而导致了 ctx.logger.error(error) 时堆内存的雪崩。

在 Node.js 性能平台 提供的 核心转储告警 + 在线分析能力 的帮助下,此类无法获取到常规 CPU Profile 和堆快照等信息的进程无故崩溃问题也变得有迹可循了,实际上它作为一种兜底分析手段,在很大程度上提升了开发者真正将 Node.js 运用到服务端生产环境中的信心。

原文链接
本文为云栖社区原创内容,未经允许不得转载。

Node.js 应用故障排查手册 —— 雪崩型内存泄漏问题相关推荐

  1. Node.js 应用故障排查手册 —— 正确打开 Chrome devtools

    楔子 前面的预备章节中我们大致了解了如何在服务器上的 Node.js 应用出现问题时,从常规的错误日志.系统/进程指标以及兜底的核心转储这些角度来排查问题.这样就引出了下一个问题:我们知道进程的 CP ...

  2. Node.js 应用故障排查手册 —— 大纲与常规问题指标简介

    楔子 你是否想要尝试进行 Node.js 应用开发但是又总听人说它不安全.稳定性差,想在公司推广扩张大前端的能力范畴和影响又说服不了技术领导. JavaScript 发展到今天,早已脱离原本浏览器的战 ...

  3. Node.js 应用故障排查手册 —— 冗余配置传递引发的内存溢出

    楔子 前面一小节我们以一个真实的压测案例来给大家讲解如何利用 Node.js 性能平台 生成的 CPU Profile 分析来进行压测时的性能调优.那么与 CPU 相关的问题相比,Node.js 应用 ...

  4. Node.js 应用故障排查手册 —— 类死循环导致进程阻塞

    类死循环导致进程阻塞 楔子 在实践篇一中我们看到了两个表象都是和 CPU 相关的生产问题,它们基本也是我们在线上可能遇到的这一类问题的典型案例,而实际上这两个案例也存在一个共同点:我们可以通过 Nod ...

  5. Node.js 应用故障排查手册 —— 综合性 GC 问题和优化

    楔子 本章前面两节生产案例分别侧重于单一的 CPU 高和单一的内存问题,我们也给大家详细展示了问题的定位排查过程,那么实际上还有一类相对更复杂的场景--它本质上是 V8 引擎的 GC 引发的问题. 简 ...

  6. Node.js 应用故障排查手册 —— Node.js 性能平台使用指南

    楔子 前一节中我们借助于 Chrome devtools 实现了对线上 Node.js 应用的 CPU/Memory 问题的排查定位,但是在实际生产实践中,大家会发现 Chrome devtools ...

  7. Node.js 应用故障排查手册 —— 利用 CPU 分析调优吞吐量

    楔子 在我们想要新上线一个 Node.js 应用之前,尤其是技术栈切换的第一个 Node.js 应用,由于担心其在线上的吞吐量表现,肯定会想要进行性能压测,以便对其在当前的集群规模下能抗住多少流量有一 ...

  8. Atitit.播放系统的选片服务器,包厢记时系统 的说明,教程,维护,故障排查手册p825...

    Atitit.播放系统的选片服务器,包厢记时系统 的说明,教程,维护,故障排查手册p825 1. 播放系统服务器方面的维护 2 1.1. 默认情况下,已经在系统的启动目录下增加了俩个启动项目 2 1. ...

  9. 电脑断网分析(故障排查手册)- 自救篇

    电脑断网分析(故障排查手册)- 自救篇 发现断网之后,应该先怎么办? 网上冲浪,突然断网,非常不爽: 发现断网之后,应该先怎么办? 特总结几点如下,应对突发状况: 光猫重启,尚未拨号? 路由器水晶头接 ...

最新文章

  1. 专访 | 社科学院和美术学院毕业生与大数据的故事【第一届数据故事计划】
  2. 零基础学python爬虫-零基础如何学爬虫技术?一篇带你入门!(理论+实操+荐书)...
  3. 如何下载和离线安装Chrome的CRX扩展文件包
  4. mysql run sql files_如何在Eclipse DTP中運行多個.sql文件
  5. 从阿里核心场景看实时数仓的发展趋势
  6. OpenGL入门程序一:绘制简单的矩形
  7. 微软官方确认!要在Edge中采用Chromium,还会带到Mac上
  8. T-Sql(二)事务(Transaction)
  9. jvisualVM调优案例
  10. scikit-learn的高级介绍
  11. 请领导批阅文件怎么说_汇报措辞:你懂得怎样向领导汇报吗(审阅、审批、批阅、批示、查阅)?...
  12. ktv点歌系统服务器破解,欧凯KTV卡拉OK点歌系统
  13. Mysql DBA 高级运维学习之路-mysql数据库乱码问题
  14. Logstash系列: mutate拦截器的使用
  15. 六.爬虫--京东登录破解(二)
  16. 高中数学40分怎么办_高一数学考40分还能拯救吗
  17. Python多线程实现WIFI破解
  18. wdm驱动的学习初步
  19. 511遇见易语言调用百度OCR文字在线本地识别及游戏画面时时识别
  20. java利用条件运算符的嵌套来完成此题:学习成绩 =90分.....(java50道经典编程题)

热门文章

  1. armbian nginx 部署博客_通过Git将Hexo博客部署到服务器
  2. matlab保存数据到excel_Excel意外退出数据未保存?这个方法可以帮你找回所有数据...
  3. 32岁了学python来的及吗_现在27岁学python来得及吗?
  4. 怎么制作游戏脚本_精彩的游戏视频混剪怎么做?录屏剪辑一站式制作
  5. delphi datasnap断线后再次连接_电脑连接WiFi后经常出现断线断开连接问题的解决方法...
  6. java jbutton 不显示_java – JButton中的图像未显示
  7. java互斥锁的实现原理_java-深入分析synchronized原理
  8. vim函数跳转 php,求助!! vim-gvim中如何让其显示函数及其参数!!
  9. linux php 守护进程,PHP程序员玩转Linux系列 使用supervisor实现守护进程
  10. signature=adf15bd90b83b628c647a1aa64741773,Thoracic Oncology