对于Node服务的内存泄露,大家怎么看?
发布于 2 年前 作者 zhuyingda 3688 次浏览 来自 问答

近期的工作中,开始从纯前端转向为Node+前端,遇到最大的一个难题就是内存泄露。 我遇到的问题是:一个日均几千万pv的Node服务,在进程运行超过10个小时之后内存量就会接近1.4G的默认内存上限,然后进程就会报出stack trace的内存溢出,然后被线上环境的pm2重启,然后内存缓慢增长 直至10多个小时之后再一次溢出 pm2重启,如此往复… 问题1:这样程度的问题算不算内存泄露? 问题2:线上环境内存溢出之后被pm2重启进程,对于20-30qps的node服务来说影响大不大? 问题3:这样的内存溢出问题该如何定位内存溢出的点呢,因为线上环境的centos版本较老 而且线上机环境不太容易安装 像memwatch v8-profiler等调试模块进行堆内存快照分析,线下似乎又很难模拟出线上的情况。我把内存溢出定位在了一个module内,但是在线下环境对这个module进行各种benchmark,将snapshot放到profiler里进行comparison显示根本没有内存泄露

真的很头疼,请教各位大神的看法

14 回复

内存泄漏目前应该还是没有什么有效的检查手段,很大程序上靠经验, 深入浅出那本书提出了三种类型的内存泄漏即:

  • 内存当缓存用不小心使得存的数据不断暴涨
  • 作用域没有即时的释放相关引用
  • 队列消费不及时(例如简单的生产者消费者模型, 产能过剩引起数据堆积).

当然上面所讲的都是宏观层面的, 具体到程序中主要需要自己检查了, 例如除了我们自己写代码产生的内存泄漏之外,对于引入的一些第三方模块使用不当也可能发生内存泄漏,例如上次碰到的一个pm2.connect这个函数, 其实这个方法只需要调用一次以确保pm2正常工作了, 但是如果你连续多次调用就会导致内存泄漏, 这是因为该模块内有个事件监听导致的作相关变量未销毁.

目前查找手段的话本质上大都都是打出heapdump.snapshot快照查看大对象方式的,当然也有个做了OneHeap的(点这里查看, 这个也只能针对打出的快照小于5M左右, 大了的话根本不可用, 这个可以分析相关引用,参考下也有助于理解快照

期待你解决问题了也分享下~~

@ncuzp 嗯 我就是内存当缓存用的情况,不过我写了一套LRU算法的实现,理论上数据不会持续暴涨。但从目前线上机运行时内存的曲线来看,是存在内存泄露的,问题也确实定位在了自己写的这个缓存模块里。然而,追查这个问题的难度在于我们的线上机器环境导致无法直接安装像v8-profiler 或者memwatch等类似的npm包,因为这些模块都是需要编译C++ addon的。在线下测试环境上面,我尝试模拟线上高并发环境对这个缓存模块的读写,然而对进程定时快照之后发现,并没有明显内存泄露迹象。 这个问题牵扯到我们线上机的稳定性,我肯定会继续研究直到得出结论的。同时,感谢你的回复

我们服务也遇到了,通过监控和定位,发现是网络请求会出现网络异常,导致http没有及时关闭,作用域没有及时释放内存,这种问题不是很严重的内存泄露,你可以升级node试下

遇到一样的问题,目前在看 Node.js 调试 GC 以及内存暴涨的分析 楼主如果解决了的话分享一下哈

定时任务,内存暴涨,只有靠pm2的max memory restart

如果在测试服务复现,那还是用alinode吧,不过是收费的。

另外, 1,为啥要在进程里存数据呢?这样你的应用就不能横向扩展了,甚至连开多个进程都不行。 2,控制不好数据的生成和回收,很容易就溢出了,占用了应用本身的内存。 3,按照正常的js写法,其实很难溢出的,通过代码做一次排查吧。 4,是不是http服务或者其他io(例如数据库)达到瓶颈了,导致io队列堆积,内存和cpu都会暴涨。

检查代码中是不是使用了占用内存不断变大的全局变量,这种变量需要自己做回收。

讲一下我个人的方法: 首先使用压测方法,因为只有长时间的压测才能定位到问题。 例如可以使用并发100,总请求10000的数据。 打断点,两个断点分析heapdump抓取内存快照,分别是5000请求与10000请求的时候的抓取到快照以后,使用chrome的profile进行对比分析,查看其中的内存变化,具体是因为哪些对象增多导致的。

我自己之前的两次 node debug 记录, 没有实际解决问题, 但也许会给你一些启发。 https://github.com/Chunlin-Li/Chunlin-Li.github.io/blob/master/blogs/javascript/node_debug_20160305.md https://github.com/Chunlin-Li/Chunlin-Li.github.io/blob/master/blogs/javascript/node_debug_20160524.md

后来内存问题解决了, 给每一个请求加一个 timeout, 比如 500ms, 超过这个时间, 则直接返回个空。 至少这个方案对我的情况有效。node单进程 qps 100左右。 原因可能是有些请求没能尽快响应, 或是没响应, 导致对应的请求一直停留在内存中。

另外, 线上环境抓 dump 也很容易。 我 node 跑在 docker 里也照样抓。 写段代码, 轮训检查 process.memUsage(好像是这个名), 如果 heap 超过 XXX, 就关闭 setInterval, httpServer close. 启动 heapdump, dump 到 /tmp 或者随便你喜欢的路径。 dump 成功后进程自杀。 睡一觉第二天上班捡尸体。

回到顶部