线上环境居然遇到了内存泄露,经过 3 天的摸索,算是解决了:
更换 pm2 版本,由 v3.5.1 降为 v3.4.1
对于这个结论还是不太满意,算是歪打正着。不过还是记录下这几天积累的经验,说不定对正在看此文的你会有帮助。
前言(鼓励)
有幸,我这次有充足的时间去排查,没有其他事情干扰。但线上出现内存泄漏,解决起来比业务代码 bug 更难解决,那种头皮发麻的难受。原因如下:
- 项目代码复杂。排查“泄漏点”,犹如大海捞针
- 开放的前端模式。顾及不暇的 node_modules 模块
- 无形的压力。需要时间拿数据做佐证,时间等的越长,压力越来
不管有没有精力 or 能力,我认为 解决内存泄漏的最佳实践是:积极的心态 + 冷静的问题定位。(没错,这是对目前没有解决问题的你说的)
内存增长的原因
可以看下这篇文章 Finding And Fixing Node.js Memory Leaks: A Practical Guide
- 全局变量
- 代码缓存
- 闭包
- …
Anyway,其实不管什么原因,主要还是 各种引用 得不到 V8 GC 的释放。扔一段很经典的代码:
1 | function calc(data) { |
执行没多久,就从 20M 飚到 几百 M。究其原因,还是因为闭包引用没有及时被销毁。
具体原因如下:
虽然 unused 没有被调用,但是其中包含 originalThing 并指向 theThing ,theThing 在定义时有个 someMethod 方法,其就是个闭包(可以访问到 originalThing) ,该闭包在 unused 中由于引用了 originalThing 一直没有被释放。
对内存进行“检查”
如果你对这块有查询过相关资料, heapdump 这模块应该频繁出现,这里说下如何使用它来监控项目的内存情况。当然还有 memwatch …
安装
1 | npm install heapdump -S |
如果你足够幸运,肯定会出现如下问题:
1 | error: #error This version of node/NAN/v8 requires a C++11 compiler |
原版本过低,需要更新 linux 系统的 gcc 等相关库。
参考如下安装说明:
1 | 安装 repo 仓库 |
当然系统是 window 可能还会有更坑的问题:安装 node-gyp、python 出错。
推荐如下 npm 模块:
windows-build-tools
一键安装相关组件依赖(我们只要静静的等待,因为时间有些久)。他会帮你安装 window 对应的 NET Framework,python 这些插件。
1 | npm install --global --production windows-build-tools |
使用
线上很简单的做了一个控制台输出,用于定位问题:
1 | var heapdump = require("heapdump"); |
这样就能实时看到系统的内存消耗(对比刚启动时):
1 | 2019-09-06 16:30 +08:00: [2019-09-06T16:30:06.700] [DEBUG] transfer - memory before 55.5898 MB memory now: 95.1484 MB diff increase 39.5586 MB |
添加个快照路由,在按照需要抓取内存此刻使用情况:
1 | router.all("/snapshot", async (ctx, next) => { |
导入到 chrome 的 profile 面板中,对比前后两个文件的变化,定位问题
一切顺利就能很快定位到问题代码。但实际要更困难,更摸不着头脑。
猜测的可能点
如果按照上述的“检查”操作还是没有定位到问题点,可能这段会对你有所帮助。
首先要知道自己着手的项目的用途,所用技术,它对你排查问题更有指向意义。
比如:此项目是基于 node 的中间层服务,对 api 接口进行转换。用于由后端 api 服务的“升级”平滑各客户端的发版时差。(服务可能在 api 调用上有性能瓶颈?)
技术栈:sequelize + koa + pm2 (熟悉项目的主要框架,从大技术方向着手)
有幸有个 项目 B 和此项目类似,技术上略有差异。
综上所述,就猜测了几个可能的 内存泄露 原因(附参考文章):
- 代码问题
- 代码逻辑全局缓存问题
- 项目本身负载能力
- 访问量(项目 A 高于 项目 B)
- 对数据库的查询的冲击(sequelize)
- 日志读写堆积(log4js)
- 第三方依赖
- pm2
验证
可能原因 | 测试方式 | 验证结果 | 备注 |
---|---|---|---|
代码问题 | ab 压测 | ok | 但需增加重视 |
sequlize 版本问题 | ab 压测 | ok | 暂不尝试。目前使用 4.42.0,线上无法承担更换版本的风险 |
Ladash _.template | ab 压测 | ok | 保持现状 |
log4js 有背压问题 | ab 压测 | ok | pm2 & log4js 使用不够友好,在有替代方案前(比如 winston),保持现状 |
pm2 版本问题 | 线上降低版本 3.5.1 -> 3.4.1 | 待验证 | 项目 B 使用 3.4.1 |
结论
这两天从线上情况上看,修改 pm2 版本后,内存泄漏得到控制。下面由此结论反推验证步骤:
使用 devtools ,发现一天的内存差异,TIMERWRAP 指标突增:
TIMERWRAP 是 Node 里 Timer 相关定义,猜测是否有定时器在有规律的无限刷新占用性能。
继续查看,发现 pm2 有些“格格不入”
metrics 心跳检测,是否有 setTimeout 之类的代码?
查看资料,发现相关 issus 中说代码有 leak
查看线上项目相关代码,的确有这段问题代码:
对于为何这段 if/else 会造成内存泄漏,有空再研究下。估计类似 dom 的 event 绑定没有解除所致。
参考
我只是知识点的“加工者”, 更多内容请查阅原文链接 :thought_balloon: , 同时感谢原作者的付出:
- JavaScript 内存泄漏教程
- 深入解析 ES Module
- Difference between Map and WeakMap in JavaScript
- ES6 Map vs WeakMap vs plain Objects – Describing the differences
- sequelize 4.37.7 造成内存泄漏
- sequelize 中 ladash api 变量未回收
- PM2 cluster + log4js?并不理想的组合
- pm2 memory leak
- 记录一次安装 heapdump,报 node-gyp rebuild failed 的问题
- Finding And Fixing Node.js Memory Leaks: A Practical Guide
- JavaScript 内存优化
- An interesting kind of JavaScript memory leak
- 4 类 JavaScript 内存泄漏及如何避免
- JavaScript 闭包详解