居然遇到了线上环境的内存泄漏。摸索了3天,解决了:把pm2版本从v3.5.1改成v3.4.1,这个结论还是不够太满意了,算是正轨了。不过还是把这几天积累的经验记录下来,说不定对正在看这篇文章的你有所帮助。前言(鼓励)还好这次我有足够的时间去调查,没有其他的干扰。但是线上出现内存泄露,比业务代码bug更难解决,让人头皮发麻。原因如下:项目代码复杂。排除“泄漏点”故障就像在开放的前端模型中大海捞针。压倒node_modules模块的无形压力。获取数据作为证据需要时间。等待的时间越长,你的压力就越大。不管你有没有精力和能力,我认为解决内存泄漏的最佳实践是:积极的心态+冷静的问题定位。(是的,这是给至今还没有解决问题的你。)内存增长的原因可以在这篇文章FindingAndFixingNode.jsMemoryLeaks:APracticalGuideGlobalvariablecodecacheclosure...总之,其实不管是什么原因,主要还是V8GC无法释放各种引用。抛出一段很经典的代码:console.log(newDate(),"memorynow:",calc(mem.rss));}vartheThing=null;varreplaceThing=function(){logger();varoriginalThing=theThing;varunused=functionfoo(){if(originalThing){console.log("未调用,但originalThing有对someMethod的引用");}};theThing={longStr:newArray(1000000).join("*"),someMethod:function(){console.log("什么都没做,但我是一个闭包");}};console.log("parse");};setInterval(replaceThing,10);执行没多久,就从20MBiao变成了几百M。原因是闭包引用没有及时销毁。具体原因如下:unused虽然没有被调用过,但是里面包含了originalThing并且指向了theThing。TheThing在定义的时候有一个someMethod方法,是一个闭包(可以访问originalThing)。没有被释放。“检查”内存。如果你查询过相关资料,heapdump模块应该是经常出现的。下面介绍如何使用它来监控项目的内存。当然还有memwatch...安装npminstallheapdump-S运气好的话肯定会出现如下问题:error:#errorThisversionofnode/NAN/v8requiresaC++11compiler原始版本太低了,需要更新linux系统的gcc等相关库。参考如下安装说明:#安装repo仓库wgethttp://people.centos.org/tru/devtools-2/devtools-2.repomvdevtools-2.repo/etc/yum.repos.d#安装新库yuminstalldevtoolset-2-gccdevtoolset-2-binutilsdevtoolset-2-gcc-c++#备份原库mv/usr/bin/gcc/usr/bin/gcc-4.4.7mv/usr/bin/g++/usr/bin/g++-4.4.7mv/usr/bin/c++/usr/bin/c++-4.4.7#创建快捷方式,将新库链接到所需目录ln-s/opt/rh/devtoolset-2/root/usr/bin/gcc/usr/bin/gccln-s/opt/rh/devtoolset-2/root/usr/bin/c++/usr/bin/c++ln-s/opt/rh/devtoolset-2/root/usr/bin/g++/usr/bin/g++#查看版本,确保生效。gcc--version当然系统是window,可能坑比较多:安装node-gyp和python时出错。推荐以下npm模块:windows-build-tools一键安装相关组件依赖(只能静静等待,时间比较长)。他会帮你安装相应的NETFramework和windows的python插件。npminstall--global--productionwindows-build-tools在线简单的控制台输出定位问题:varheapdump=require("heapdump");letstartMem=process.memoryUsage();functioncalc(data){returnMath.round((data/1024/1024)*10000)/10000+"MB";}//使用koarouter.all("/foo",async(ctx,next)=>{letmem=process.memoryUsage();logger.debug("之前的内存",calc(startMem.rss),"现在的内存:",calc(mem.rss),"diffincrease",calc(mem.rss-startMem.rss));//...});这样就可以实时看到系统的内存消耗情况(对比刚启动时):2019-09-0616:30+08:00:[2019-09-06T16:30:06.700][DEBUG]传输-55.5898MB之前的内存现在内存:95.1484MBdiff增加39.5586MB2019-09-0616:30+08:00:[2019-09-06T16:30:07.724][DEBUG][传输-56.2148之前的内存现在MB内存:69.8438MBdiffincrease13.6289MB2019-09-0616:30+08:00:[2019-09-06T16:30:10.406][DEBUG]transfer-memorybefore56.2148MBnow70.5977MBdiffincrease14.3828MB2019-09-0616:30+08:00:[2019-09-06T16:30:11.018][DEBUG]传输-内存之前55.5898MB内存现在:95.4219MBdiff增加39.832MB2019-09-0616:30+08:00:[2019-09-06T16:30:12.827][DEBUG]befor传输-内存现在55.5898MB内存:95.6797MBdiff增加40.0898MB2019-09-0616:30+08:00:[2019-09-06T16:30:12.952][DEBUG]传输-55.5898MB内存96之前的内存cremeiff:839.3789MB添加快照路由,根据需要抓取当前内存使用情况:router.all("/snapshot",async(ctx,next)=>{heapdump.writeSnapshot("./dump-"+Date.now()+".heapsnapshot",function(err){if(err)console.error(err);});});导入到chrome的profile面板中,对比前后两个文件的变化,顺利的话定位问题有问题的代码可以快速定位,但其实更难,更容易混淆。可能的猜测点如果你按照上面的“查”操作还是没有定位到问题点,或许这一段对你有所帮助。首先,你必须清楚你所从事的项目的目的和你使用的技术,这对你排查问题更有意义。例如:本项目是一个基于node的中间层服务,对api接口进行转换。用于后端api提供的“升级”,以消除每个客户端的发布时间差异。(可能服务在api调用上有性能瓶颈?)技术栈:sequelize+koa+pm2(熟悉项目主要框架,从大技术方向入手)幸好有个项目B和这个项目类似,技术略有不同。综上,我猜测了几种可能导致内存泄露的原因(附参考文章):代码问题、代码逻辑、全局缓存问题、JavaScript中Map和WeakMap的区别、项目本身的负载能力接入(项目A高于项目B)数据库查询的影响(sequelize)sequelize4.37.7导致内存泄漏sequelize中的Ladashapi变量没有被回收日志读写累积(log4js)PM2集群+log4js?组合不理想第三方依赖pm2pm2内存泄露验证可能原因测试方法验证结果备注代码问题ab压测ok但要多注意sequlize版本问题ab压测ok暂未尝试。目前使用的是4.42.0,不能承担在线改版的风险。ladash_.templateab压测ok维持现状log4js有背压问题ab压测okpm2&log4js使用不够友好,直到有替代品(比如winston),维持现状pm2版本问题线上版3.5.1->3.4.1待验证项目B使用3.4.1结论从这两天网上的情况来看,修改了pm2版本后,内存泄漏得到了控制。下面的结论用来逆向验证步骤:使用devtools,发现内存差了一天,TIMERWRAP指标突然变大:TIMERWRAP是Node中Timer的定义,猜测是否有定时无限刷新的定时器占用性能。继续查看,发现pm2有一些“跑调”的metrics心跳检测。有setTimeout之类的代码吗?查资料发现相关issus说代码有漏洞。查了一下网上项目的相关代码,确实有这段有问题的代码:至于为什么这个if/else会导致内存泄露,有空再研究一下。估计类似dom的事件绑定还没有放出来。作为参考,我只是一个知识点的“处理器”。更多信息请参考原文链接,感谢原作者投稿:JavaScript内存泄漏教程深入分析ES模块ES6中Map和WeakMap的区别ES6MapvsWeakMapvsplainObjects–区别描述续集4.37。7导致内存泄漏sequelize中的Ladashapi变量没有回收PM2集群+log4js?不是一个理想的组合pm2memoryleak记录heapdump安装,报告node-gyprebuildfailed问题FindingAndFixingNode.jsMemoryLeaks:APracticalGuideJavaScriptmemoryoptimization一种有趣的JavaScript内存泄漏4类JavaScript内存泄漏以及如何避免JavaScript关于我的关闭详解如果您觉得本文对您有帮助,请点赞或分享给更多的道友。也可以扫描二维码关注我的微信订阅号——【前端雨爸】,第一时间收到技术文章,下班后我会继续输出。最后,感谢您的阅读,您的支持是我写作最大的动力
