本文发表于北斗同构github, 转载请注明出处
注: 本文为第12届D2前端技术论坛《打造高可靠与高性能的React同构解决方案》分享内容,已经过数据脱敏处理。
菜鸟物流大市场是菜鸟旗下的一条业务线,可以简单地理解为物流领域的淘宝,是为撮合物流需求方和物流提供方搭建的一个平台。其中搜索页、详情页、买家中心等页面是基于beidou同构框架开发的。随着node、react同构等技术越来越广泛地使用, 内存泄漏的事情时有发生,应当引起足够的重视。最近在做菜鸟物流市场的技术支持,就“中奖”了,把实践过程中的经验和心得整理了下,供大家参考。
先介绍几个基本术语:
回到之前说到的菜鸟物流大市场
菜鸟物流大市场上线之后,经常收到alimonitor的告警通知,如下图
于是打开了alinode查看慢日志, 果然有不少慢日志记录
当时主要有以下几个现象:
根据当时的现象做了简单的分析并制定了具体的action:
从上面的推断来看,发生内存泄漏的可能性非常大,但仍然需要通过实际数据进行验证,于是根据制定的action进行数据采集
再次发布之后,采集到了数据:
从上图中可以看出, 随着时间的推移,进程1694的hsf调用耗时始终稳定,但是服务端渲染的时间却逐步飙升到3700多毫秒,然后在某个临界值之后瞬间降低到50毫秒左右。可能是由于某某事件( 猜测是内存泄漏引起OOM )导致了进程崩溃,接下来beidou框架会自动重启进程又恢复良好的状态。打开sandbox一看进程生命周期,果然如此, 进程1694挂了,然后重新启动了一个29649进程。
从上图中也可以看到RSS(实际使用物理内存)高达1880.93MB,至此基本上可以确定是内存泄漏了。查看内存占用曲线,内存呈现锯齿状,先一路飙升,到达零界点之后瞬间下降,如此周而复始。和我们的推断完全一致,这是典型的内存泄漏曲线。
最终结论: 访问速度慢是因为内存泄漏消耗了过多的资源
定位到是内存泄漏之后,还需要进一步排查具体是什么代码导致了内存泄漏。这时候就要用到排查神器 - alinode了。
先创建堆快照:
在分析页面打开对象簇视图
, 可以看到里面有大量的Window对象, 搜索下竟然高达390个
采样了几个Window对象,通过GC Root
展开,发现挂载了无数个定时器。
分析代码找到了两处定时器的设置,看代码逻辑,该定时器在服务端根本不会被释放。
componentWillMount(){
let _this = this;
window.handler = window.setInterval(function(){
if(typeof AMap){
_this.renderMap('', AMap);
window.clearInterval(window.handler);
}
}, 300);
}
注释掉之后在预发验证没有再出现window相关的内存泄漏。
PS.
后来的验证发现,除了定时器的问题,还有另外两处内存泄漏,不再赘述, 贴上其中一处(高德地图)内存泄漏的代码供读者参考
componentWillMount(){
this.createAmapScript();
}
createAmapScript(){
let script = document.createElement('script'),
body = document.getElementsByTagName('body')[0];
script.type = 'text/javascript';
script.src = 'https://webapi.amap.com/maps?v=1.3&key=59699a8cfee7c52f58390357cbdbf27d';
body.appendChild(script);
}
从上述两处代码可以看出,定时器无需在服务端执行, 而高德地图本身就不支持服务端渲染,因此可将二者放到客服端渲染即可。根据react的特性,componentDidMount生命周期函数在服务端不会执行,因此将上述代码从componentWillMount移到componentDidMount中即可。具体修复如下:
通过loadtest在本地压测验证下:
单个进程同样以10个QPS进行施压,对比下可以看出,修复前RT时间一路上升,而修复后RT始终稳定在200毫秒左右。
再看看线上数据, 内存占用率始终稳定,没有出现飙升现象。
至此,打完收工。
看完了案例,是时候系统化地总结下方法论了。
从刚才的案例中可以看出来,内存泄漏最典型的现象就是内存占用率会随着时间的推移而逐步上升,就算没有流量了,内存占用率也不会下降。而健康的应用是流量上升内存占用会上升,而流量下降之后内存占用率就会回到原水平。
通常造成内存泄漏的有以下几个因素
本文中的案例就属于作用域未释放
上医治未病,中医治欲病,下医治已病
,说的是医术最高明的医生并不是擅长治病的人,而是能够预防疾病的人。让问题在开发阶段就暴露出来, 而不是等到线上告警了再抢救。constructor
中做事件绑定,建议放到componentDidMount生命周期中此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。