NodeJs内存占用过高如何排查

这篇文章给大家介绍NodeJs内存占用过高如何排查,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

问题起因

最开始是因为一个定时功能上线后,线上的容器自动进行了扩容,由于 NodeJs 服务本身只有一些接口查询和 socket.io 的功能,一没大流量,二没高并发的一个服务居然需要扩容 8 个容器(一个容器分配的是 2G 的内存),想到这里怀疑是内存泄漏了。同时日志中偶发的看到内存不足。

NodeJs内存占用过高如何排查

扩容原因

问了运维同学查到是由于内存占用到临界值导致的扩容。

负载情况

首先排除一下是不是因为服务压力过大导致的内存占用升高,可能这是一种正常的业务现象。

NodeJs内存占用过高如何排查

通过监测,发现流量和 CPU 占用都不是很高,甚至可以说是很低,那么这么高的内存占用是属于不正常的现象的。

因为是内存原因导致的,而且有逐步持续上升的现象,所以就联想到了内存泄漏这个方向,常用的做法是打印「堆快照」即 heapsnapshot 文件。

进入容器:

go 节点名称

进入 NodeJs 项目的文件夹

/usr/local/app/taf/service_name/bin/src

生成快照:

constheapdump=require('heapdump');
heapdump.writeSnapshot('./'+newDate().getTime()+'.heapsnapshot',function(err,filename){
console.log('dumpwrittento',filename);
});

受限于容器内使用 lrzsz 命令直接传输文件很慢,因此需要使用 scp 命令传输到一台静态资源服务器上,可以通过浏览器进行下载的。

scp 1620374489828.heapsnapshot username@ip:/data/static/snapshot

对比 heapsnapshot

在服务启动后,和运行一段时间后的生成两次快照内容,对比后的排序也只能大致看到 Websocket Socket 这些关键字。

NodeJs内存占用过高如何排查

进一步展开也无法定位到是否由某个函数引起的。

NodeJs内存占用过高如何排查

从快照里面似乎找不到什么线索,由于整个工程的业务量代码并不是很大,因此逐行 review 排查,但是似乎也没有什么异常的写法会引起 oom,其实业务代码小还好,如果是个大工程的话,这种做法没有性价比,还是需要通过一些诊断手段来排查,而不是直接去 codereview。

反复打印了几次快照,看了几遍后,还是看到 websocket 这些字眼,因而考虑到是否是因为 socket 链接未释放导致的问题呢?

Google 关键字搜了一下 WebSocket memory leak ,还真有,解决方案是加上 perMessageDeflate ,禁用压缩。目前低版本的 socket-io 默认是开启的,于是我加了之后观察了一段时间的内存占用,并未有明显的下跌,发布后,内存占用依旧很高。

配置语法:

require('socket.io').listen(server, {perMessageDeflate: false});

客户端发送的请求中含有这个字段:

NodeJs内存占用过高如何排查

首先这个参数是用来压缩数据的,client 端默认是开启,server 端是关闭的,出于某些原因,开启后会导致内存和性能的消耗,官方建议是考虑后再决定是否开启。但是低版本的 socket-io 是开启的,比如 ^2.3.0 的版本(貌似是 bug,后续版本已经改为默认关闭)。

The extension is disabled by default on the server and enabled by default on the client. It adds a significant overhead in terms of performance and memory consumption so we suggest to enable it only if it is really needed.

https://github.com/socketio/socket.io/issues/3477#issuecomment-610265035

开启后,内存仍旧居高不下。

NodeJs内存占用过高如何排查

console.log

另外一个现象就是现有的 Node 服务会打印一些日志,翻了一些网上的 NodeJs 内存泄漏的文章,有看到 console 日志输出导致的泄漏的情况,因此注释掉 console 之后继续观察内存占用,结果仍旧是内存高占用。

线索到这里似乎就断掉了,没有头绪了。

日志

过了一天后,无意中看了一下日志文件,由于服务启动的时候会打印一些启动日志,发现有重复输出的情况:

NodeJs内存占用过高如何排查

说明有重复运行的情况,为了验证这一猜想,使用 top 命令查看。

TOP 命令

同时还想看一下具体的内存占用。发现居然有这么多的 worker process ,根据当前业务的实际使用情况不应该只有 2 ~ 4 个就够了么,为什么要开这么多的子进程。

PIDUSERPRNIVIRTRESSHRS%CPU%MEMTIME+COMMAND
90359username200736m38m14mS0.00.00:07.30/usr/local/app/service_name/bin/src/index.js:workerprocess
90346username200864m38m14mS0.30.00:07.08/usr/local/app/service_name/bin/src/index.js:workerprocess
90381username200730m38m14mS0.30.00:08.75/usr/local/app/service_name/bin/src/index.js:workerprocess
90366username200804m37m14mS0.00.00:06.94/usr/local/app/service_name/bin/src/index.js:workerprocess
90618username200730m37m14mS0.00.00:08.42/usr/local/app/service_name/bin/src/index.js:workerprocess
90326username200736m37m14mS0.00.00:08.46/usr/local/app/service_name/bin/src/index.js:workerprocess
90542username200736m37m14mS0.00.00:08.85/usr/local/app/service_name/bin/src/index.js:workerprocess
90332username200799m37m14mS0.00.00:07.32/usr/local/app/service_name/bin/src/index.js:workerprocess
90580username200732m37m14mS0.30.00:08.94/usr/local/app/service_name/bin/src/index.js:workerprocess
90602username200731m37m14mS0.30.00:08.33/usr/local/app/service_name/bin/src/index.js:workerprocess
90587username200735m37m14mS0.00.00:08.83/usr/local/app/service_name/bin/src/index.js:workerprocess
90568username200731m37m14mS0.00.00:08.83/usr/local/app/service_name/bin/src/index.js:workerprocess
90544username200729m37m14mS0.00.00:09.07/usr/local/app/service_name/bin/src/index.js:workerprocess
90556username200729m37m14mS0.00.00:08.82/usr/local/app/service_name/bin/src/index.js:workerprocess
90431username200735m37m14mS0.00.00:08.29/usr/local/app/service_name/bin/src/index.js:workerprocess
90486username200729m37m14mS0.00.00:09.06/usr/local/app/service_name/bin/src/index.js:workerprocess
90516username200735m37m14mS0.00.00:08.95/usr/local/app/service_name/bin/src/index.js:workerprocess
90465username200729m37m14mS0.00.00:09.06/usr/local/app/service_name/bin/src/index.js:workerprocess
90527username200735m37m14mS0.00.00:08.46/usr/local/app/service_name/bin/src/index.js:workerprocess
90487username200732m37m14mS0.30.00:08.48/usr/local/app/service_name/bin/src/index.js:workerprocess
90371username200731m37m14mS0.30.00:08.75/usr/local/app/service_name/bin/src/index.js:workerprocess
90423username200729m36m14mS0.30.00:08.09/usr/local/app/service_name/bin/src/index.js:workerprocess
90402username200729m36m14mS0.30.00:08.96/usr/local/app/service_name/bin/src/index.js:workerprocess
90500username200729m36m14mS0.00.00:08.70/usr/local/app/service_name/bin/src/index.js:workerprocess
90353username200729m36m14mS0.30.00:08.95/usr/local/app/service_name/bin/src/index.js:workerprocess
90636username200729m36m14mS0.00.00:08.84/usr/local/app/service_name/bin/src/index.js:workerprocess
90425username200732m36m14mS0.00.00:08.78/usr/local/app/service_name/bin/src/index.js:workerprocess
90506username200729m36m14mS0.00.00:08.84/usr/local/app/service_name/bin/src/index.js:workerprocess
90589username200729m36m14mS0.30.00:09.05/usr/local/app/service_name/bin/src/index.js:workerprocess
90595username200729m36m14mS0.00.00:09.03/usr/local/app/service_name/bin/src/index.js:workerprocess
90450username200729m36m14mS0.30.00:08.97/usr/local/app/service_name/bin/src/index.js:workerprocess
90531username200729m36m14mS0.00.00:08.99/usr/local/app/service_name/bin/src/index.js:workerprocess
90509username200735m36m14mS0.00.00:08.67/usr/local/app/service_name/bin/src/index.js:workerprocess
90612username200730m36m14mS0.30.00:08.84/usr/local/app/service_name/bin/src/index.js:workerprocess
90479username200729m36m14mS0.00.00:08.58/usr/local/app/service_name/bin/src/index.js:workerprocess
90609username200731m36m14mS0.30.00:09.23/usr/local/app/service_name/bin/src/index.js:workerprocess
90404username200734m36m14mS0.30.00:08.78/usr/local/app/service_name/bin/src/index.js:workerprocess
90395username200736m36m14mS0.00.00:08.57/usr/local/app/service_name/bin/src/index.js:workerprocess
90444username200729m36m14mS0.00.00:09.04/usr/local/app/service_name/bin/src/index.js:workerprocess
90438username200729m36m14mS0.30.00:07.78/usr/local/app/service_name/bin/src/index.js:workerprocess
90340username200736m36m14mS0.30.00:07.37/usr/local/app/service_name/bin/src/index.js:workerprocess
90333username200729m36m14mS0.00.00:07.60/usr/local/app/service_name/bin/src/index.js:workerprocess
90563username200735m36m14mS0.30.00:08.93/usr/local/app/service_name/bin/src/index.js:workerprocess
90565username200734m36m14mS0.30.00:08.77/usr/local/app/service_name/bin/src/index.js:workerprocess
90457username200735m36m14mS0.00.00:08.31/usr/local/app/service_name/bin/src/index.js:workerprocess
90387username200740m36m14mS0.00.00:07.59/usr/local/app/service_name/bin/src/index.js:workerprocess
90573username200728m35m14mS0.00.00:09.06/usr/local/app/service_name/bin/src/index.js:workerprocess
90472username200728m35m14mS0.00.00:08.94/usr/local/app/service_name/bin/src/index.js:workerprocess
90313username200588m27m13mS0.00.00:00.46/usr/local/app/service_name/bin/src/index.js:masterprocess

由于 %MEM 这一列的数值在容器内部看不出具体的内存占用,都是显示的 0.0,所以需要查看 VIRT, RES 和 SHR 这三个值,它们的含义可以在这里查看: https://www.orchome.com/298

我们更关心 RES,RES 的含义是指进程虚拟内存空间中已经映射到物理内存空间的那部分的大小,因此可以发现,一个 worker process 占用了 35 ~ 38M 之间的内存大小,一共有 48 个 worker process, 一个 master process。

48 个 worker process 是怎么来的呢?通过查询 CPU 的逻辑个数,可以看到确实是 48 个。

#总核数=物理CPU个数X每颗物理CPU的核数
#总逻辑CPU数=物理CPU个数X每颗物理CPU的核数X超线程数

#查看物理CPU个数
cat/proc/cpuinfo|grep"physicalid"|sort|uniq|wc-l

#查看每个物理CPU中core的个数(即核数)
cat/proc/cpuinfo|grep"cpucores"|uniq

#查看逻辑CPU的个数
cat/proc/cpuinfo|grep"processor"|wc-l

控制进程数

由于对 Taf 平台不是很熟悉,了解到在 taf 上面运行 NodeJS 需要对应的 package: @tars/node-agent ,查了一下官网的使用文档: https://tarscloud.github.io/TarsDocs/dev/tars.js/tars-node-agent.html

有一个 -i 的配置,代表 instances

-i, –instances

node-agent 采用 Node.js 原生的 Cluster 模块来实现负载均衡。

可在此配置 node-agent 启动的子进程(业务进程)数量:

未配置(或配置为 auto 、 0 ),启动的子进程数量等于 CPU 物理核心 个数。

配置为 max ,启动的子进程数量等于 CPU 个数(所有核心数)。

如果 node-agent 是由 tarsnode 启动的,会自动读取TARS配置文件中的 tars.application.client.asyncthread 配置节。

也可通过 TARS平台 -> 编辑服务 -> 异步线程数 进行调整。

https://tarscloud.github.io/TarsDocs/dev/tars.js/tars-node-agent.html通过这个 package 启动 Taf 上的 NodeJs 服务,同时开启负载均衡的能力,由于没有配置具体的子进程(业务进程)数量,所以默认就是用了 CPU 物理核心 个数,因为是 2 个 cpu 所以再 *2,一共生成了 48 个 ‍♂️,每个 worker process 都要占用内存,所以内存占用一直居高不下。

可以在「私有模板」里修改配置:

NodeJs内存占用过高如何排查

然后重启服务,查看内存占用:

NodeJs内存占用过高如何排查

可见 worker process 数量影响了内存占用,原先内存使用率的趋势图上会持续增长(刚开始也是因此怀疑内存泄漏),这个问题在降低了 worker process 后并没有体现出来,目前暂且忽略,后续会持续观察。

为了验证重复 console 和 worker process 的关系,在开启 2 个 worker process 的情况下,查看日志,确实是打印了 2 次。

NodeJs内存占用过高如何排查

关于NodeJs内存占用过高如何排查就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

发布于 2021-05-10 20:38:28
收藏
分享
海报
0 条评论
190
上一篇:python中for x in range如何使用 下一篇:nodejs与javascript有什么区别
目录

    0 条评论

    本站已关闭游客评论,请登录或者注册后再评论吧~

    忘记密码?

    图形验证码