一次node内存管理问题的分析

起因

网站需要接入公司的一个通用脏字管理组件UIC来做脏字过滤的功能,该组件的原理是在现网机器上部署一个对方提供的agent,agent做远程脏字拉去和实时更新的工作,并提供sdk,通过mmap共享内存的方式将脏字库映射到我们后台的主进程中,从而使主进程能够读到脏字库,完成脏字过滤的功能。 然而,这个地方我们碰到了一个问题,我们使用pm2工具做后台node进程的管理,加载了UIC的SDK以后,pm2检测到node进程的内存增长了100MB多,我们一台机器是启动16个对等的node进程的,这不免让我们有些担心机器内存的使用量,于是深入调查了下内存增长的原因。

原因分析

首先询问UIC的接口人,从接口人这边得到的答案是UIC组件不光是要load脏字库,还要创建一些管道文件用作反作弊等其他功能,所以使用该SDK内存消耗会比较大,那么一个进程内存消耗增长无法避免,我们多个进程是否会消耗多份内存导致内存被耗尽?接下来就开始分析这个问题。

UIC说是通过共享内存实现的脏字库载入进程,但是我们pm2内存检测看到的是每个进程的内存都增长了100MB多,先看了下pm2内存检测的实现,pm2内存检测是通过pidusage模块去检测的,该模块是通过读取/proc/{pid}/stat获取每个进程的内存使用量,自己写了个mmap的demo程序,两个进程同时映射一个2G的文件,每个进程的/proc/{pid}中看到的内存使用量都是2G,然而使用free命令看到的内存使用量只增长了2G,这说明这两个进程虽然分别统计消耗了2G内存,但是实际一共只占用了系统2G的内存。那么使用pm2启动时虽然每个进程看起来都占用了很大的内存,加起来似乎很多,但是由于使用了mmap共享内存的原因,实际消耗的系统内存并不多。

但是这里又引申出另外一个问题,我们配置pm2时设置了参数max_memory_restart为200,语义上当改node进程消耗的内存超过200MB时,该进程就会自动重启,但是我们观察到,加入UIC sdk后,pm2检测到的内存妥妥的超过了200MB,但是进程并没有重启,是pm2这个参数失效了?经过分析,发现是我们使用的pm2版本的问题,我们使用的pm2版本是0.12.1,这个版本的pm2对这个参数的实现方式是在启动node进程时设置–max-old-space-size参数为该值,我们知道,node的内存管理实际上是分V8堆内老内存区,V8堆内新内存区,V8堆外内存的,UIC共享内存的分配方式是分配的堆外内存,而老版本的pm2只检测V8堆内老内存区是否超过阈值,所以即使pm2监控显示的内存超过了阈值也不会重启进程,解决方法是更新pm2的版本,新版本的pm2已经将这个参数的检测改为检测/proc/{pid}中的值了。

总结

深入研究了下node、共享内存和pm2的内存管理机制,对node的内存管理有了一个比较清晰的认识,但是分析到这里还有个问题,我们现网进程运行一段时间后内存消耗已经超过200MB的阈值,但是没有重启,而新启动的进程内存消耗只有100MB左右,这说明我们的后台进程可能有堆外进程的泄露,可能是引入了某个native模块导致的,这个问题需要进一步排查。

PS:最后吐槽下做UIC的这个小组,首先是接口人是个校招的小伙,毛都不懂,一个问题抛过去要处理3天,后来换了个熟手就快多了,但是这个东西环境依赖太多,异常难用,老有莫名奇妙的权限问题,而且发布模式感觉也很不成熟,需要人工找他们接口人要发布包,还需要人工找接口人添加现网发布机器IP,连相关文档都要人工找他们要,感觉作为公司的一个通用组件,这个已经挫到一定程度了。

Loading Disqus comments...
Table of Contents