精华 记录一次由一行代码引发的“血案”
发布于 7 年前 作者 DerekYeung 14830 次浏览 来自 分享

起因

我们之前的平台一直采用php+node双服务的模式进行开发,其中node一直作为中间层,没有大规模的部署,一方面由于团队node经验不足,不敢贸然替换,另一方面也听闻js一些关于内存泄漏方面的问题,故一直保持这样的架构 但是在去年,我们发现我们的业务如果采用node开发,不管是在响应速度还是在开发效率上(前端使用webpack),都要提高不少,于是我们决定把服务全线替换为node.js

开始的时候,我们使用了Koa,选择自己造轮子 我们参考借鉴了php框架yii2中的一些思路和设计模式,再结合我们的产品特点,打造了一套我们自己的node.js框架。由于大量参考yii2,导致了虽然在语言的转换上几乎没有遇到什么障碍,但与此同时我们也不得已做了很多hack。。也算是为此次的事件埋下了伏笔

问题

服务经过node.js改造之后成功上线,平滑过渡,本该是一件好事,但是我们通过监控发现,事情没有这么简单。。。 由于我们的产品在7-10点有一个使用的高峰期,但是监控发现服务经常在这个时间点挂掉,于是蹲守查看过程,发现状况如下 image.png 可以看到有部分的服务内存已飙升到1G以上,并且迟迟不回落,经过几天的测试发现。。遇到了内存泄漏问题 于是我们开始着手排查,最开始怀疑可能是hack导致,故修改了部分代码,观察几天后发现上涨速度有减慢,但依然存在泄漏

排查

在我们无计可施的时候,我们尝试使用easy-monitor来定位泄漏点,不过由于环境的原因,并没有安装成功,于是联系了作者@hyj1991 在大佬的推荐下,我们使用了alinode进行监控,在@hyj1991 的帮助下,几分钟就定位到了疑似泄漏点的地方 不过,很滑稽的是,这个泄漏点和我们预想的方向完全不同…在此也分享给大家 image.png 上面部分为产生泄漏的代码,在这里先不说为什么这样去写,先说一下这里产生泄漏的原因,根据@hyj1991 的解析,原因是这样的 image.png 也就是说,delete后由于每次重新载入的push,Module中数组的数据又无法得到释放,最终产生leak 经过@hyj1991的帮助,最终我们删除掉这部分代码,泄漏问题终于解决 (下图为修改后运行效果,可以看到内存占用很小,并且一段时间后会回落) image.png

下面是@hyj1991 使用alinode的整个过程,包含排查过程、原因分析和一波商业互吹 知乎传送门:https://zhuanlan.zhihu.com/p/34702356

另一个问题

泄漏的问题算是解决了,但其实还有另外一个问题,可能也有人会问,为什么要那样去写 其实一开始,写这个原因我是抗拒的,因为这实在是一个低级错误,但确实有必要跟大家分享一下 最初这么写的原因,是因为我们发现不知道什么原因在某个类当中操作另一个类,会影响另一个类的私有属性

大致意思上,可以这样去理解

function A(){
	//假设A有属性test
	this.test = '1';
}

function B(){
	var a = new A;
	a.test = '2';
}
var C = new B;
var D = new A;
console.log(D.test); // => 2 应该得到1,但我们得到了2

这让我们不能理解,理论上不应该这样,但就是找不到原因,猜测有可能是黑魔法导致的一些奇怪问题,最终因为偷懒+确实没找到问题所在,我们采用了用一个Model删一个Model的方式来解决。。。于是就出现了上面产生泄漏部分的代码

那么,既然泄漏已经知道是这里的问题,为了解决这个问题,我与@hyj1991 一步一步的进行分析,最终定位到这里 image.png 由于之前在开发中,也使用了condition作为检索条件,所以我们在初期的排查过程中均没有发现这里的问题(我们把它当作了私有属性),因为在后面的开发中为了统一规范,已经弃用了condition改为where来作为检索条件,于是当我看到这一句代码的时候。。 image.png 只能说。。。 image.png 所以说,并没有修改它的私有属性,只是我们一直访问和修改的是他的原型链… 将其修改后,问题解决,发布线上,目前稳定运行中…

总结

首先第一点,非常感谢@hyj1991 对我们的帮助 其次alinode是真的好用,好用还免费,绝对的良心产品 从这次可以说是很的事故中,我们算是吸取了很多教训,但同时也得到了不少宝贵的经验

  1. 遇到问题不要慌,一定要仔细排查,一步一步来,就算找不到问题点,也不要想各种野路子来解决
  2. alinode好用,牛逼
  3. 目前阿里在node方面有非常强大的团队支撑,技术水平可以说是无人能敌
  4. 框架很重要,但团队的风格统一也是很重要滴

我们目前也正在慢慢将框架转为egg.js,同时再次感谢@hyj1991 ,也感谢阿里给我们广大node开发者提供了这么优秀的产品,感谢!

关于“血案”

其实也没什么,就是写这一行代码的同学已经被我们做掉了,祭天(手动滑稽)

我知道你们看烦了,但我还是要说一句,alinode真特娘好用

image.png

传送门:

alinode

19 回复

@了我这么多次,愧不敢当,你这边给出的细节也很详细,不然看不到代碼我也不可能这么快找到问题点,哈哈

来自酷炫的 CNodeMD

@hyj1991 感谢感谢,再次感谢 哈哈

好详细,感谢分享宝贵经验 自豪地采用 CNodeJS ionic

感谢分享。

我们使用了alinode进行监控,在@hyj1991 的帮助下,几分钟就定位到了疑似泄漏点的地方

@DerekYeung 这里无图无真相啊… 用 alinode 怎么就能快速定位到这了?补几张图吧

这种问题好难定位,只有耐心才能解决。

@simongfxu 不会的,alinode 可以直接帮你很快定位到。

@atian25 说来惭愧,因为定位的过程是@hyj1991 帮忙操作的,所以对具体的操作流程没有办法分享给大家,只知道最后分析出来的结果,方便的话也可以让@hyj1991 帮忙补几张图 哈哈

@simongfxu 确实需要耐心,不过工具也很重要,如果没用alinode的话,有耐心也不一定能这么快找到问题

egg 配合ailnode配置也很简单

这个问题之前在看require模块的时候有研究过 这个博客讲的比较详细

厉害厉害,佩服佩服! 涨知识了,缓存居然在数组里 是我也可能查不到这个原因,看来删除require.cache 还不能直接用

来自酷炫的 CNodeMD

厉害厉害 之前做模块热更新也遇到过类似的问题

@atian25 看到了,这个是技术文,比我这个有用的多哈哈,我了文章里require一下

虽然漏洞解决了,但是你这样使用nodejs其实是不好的,其实是技术或者架构规划的不好导致的一系列的问题,首先我看到了你们使用prototype的类原型操作,es6就已经引入class的方式了。如果想代码更加整洁可读严谨,建议使用TypeScript写代码,然后编译成js运行

回到顶部