上篇文章:《Node.js断言方案》介绍了Node.js断言技术,现在我们再来说说有关性能调优相关技术。
性能调优
说完了Node.js
调试方面需要掌握的基本技能,下面说一下性能调优相关的技巧。
Node.js相比Java、PHP这些老牌语言,基础设施还是有所欠缺的:比如性能分析和监控工具等,加上它的单线程运行特性,在大型应用中,很容易让系统的CPU、内存或者事件队列太满达到瓶颈,从而导致程序崩溃。一旦发现程序警报CPU负载过高,或者内存飙高时,我们该如何排查Node.js代码存在的问题呢?首先先分析一下问题。
内存持高存在的因素:
- 缓存,很多人在code的时候把内存当缓存用,e.g. 使用js对象储存用户的session信息
- 闭包,作用域长时间不能被释放掉
- 生产者和消费者存在速度延迟,e.g. 数据库忙不过来,query队列堆积
CPU负载过高可能因素:
- 垃圾回收频率过高、量太大,这一般是因为内存或者缓存暴涨导致的
- 密集型的长循环计算,e.g. 大量遍历文件夹、大量计算等
- 客户端并发数太大,e.g. 利用node作为socket服务端时,客户端连接数太大时,心跳处理也属于一次网络io,最终libuv转换成事件,严重影响业务消息发送
这些问题都是让人头疼的,一个项目几十上百个文件,收到这些警报如果没有经验,根本无从下手排查。
最直接的手段就是分析 GC 日志,因为程序的一举一动都会反馈到 GC 上,而上述问题也会一一指向 GC,如:
内存暴涨,尤其是 Old Space 内存的暴涨,会直接导致 GC 的次数和时间增长
缓存增加,导致 GC 的时间增加,无用遍历过多
密集型计算,导致 GC Now Space次数增加
ps:
阿里出了一套调优工具alinode,看起来比较不错,感兴趣的童鞋可以试试,顺便反馈一下情况。😄
内存管理
有关Node.js
内存管理和垃圾回收机制,这里简单介绍一下,用心的同学可以去搜索一下这方面的资料。
V8
把内存分为 New Space 和 Old Space,New Space 的大小默认为 8M,Old Space 的大小默认为 0.7G,64位系统这两个数值翻倍。
对象的生命周期是:首先进入 New Space,然而 New Space 被平均分为两份,当一次GC运行后都会将其中一份的活着的对象复制到另一份,所以它的空间使用率是50%
,这个算法叫做 Cheney 算法,这个操作叫做 Scavenge。再过一段时间,如果 New Space 中的对象还活着,这些对象会被挪到 Old Space 中去,GC 会每隔一段时间遍历 Old Space 中死掉的对象,然后整理碎片(这里有两种模式 mark-sweep 和 mark-compact,不赘述)。上面提到的死掉的对象
指的是对象已经没有被引用了,或者说被引用的次数为零了。
分析GC日志
上面说完Node.js内存管理后,下面我们开始分析具体问题了,e.g. 假设缓存增加(比如使用对象缓存了很多用户信息),GC 是不知道这些缓存死了还是活着的,他们会不停地查看这个对象,以及这个对象中的子对象是否还存活,如果这个对象数特别大,那么GC遍历的时间也会特别长。
当我们进行密集型计算的时候,会产生很多中间变量,这些变量往往在New Space中就死掉了,那么GC也会在这里多次地进行New Space区域的垃圾回收。
如何分析?
Node在启动程序的时候添加--trace_gc
参数,当V8在进行垃圾回收的时候,会将垃圾回收的信息打印出来:
➜ $ node --trace_gc test.js
...
[94036] 68 ms: Scavenge 8.4 (42.5) -> 8.2 (43.5)
MB, 2.4 ms [allocation failure].
[94036] 74 ms: Scavenge 8.9 (43.5) -> 8.9 (46.5)
MB, 5.1 ms [allocation failure].
[94036] Increasing marking speed to 3 due to high promotion rate
[94036] 85 ms: Scavenge 16.1 (46.5) -> 15.7 (47.5)
MB, 3.8 ms (+ 5.0 ms in 106 steps since last GC) [allocation failure].
[94036] 95 ms: Scavenge 16.7 (47.5) -> 16.6 (54.5)
MB, 7.2 ms (+ 1.3 ms in 14 steps since last GC) [allocation failure].
[94036] 111 ms: Mark-sweep 23.6 (54.5) -> 23.2 (54.5)
MB, 6.2 ms (+ 15.3 ms in 222 steps since start of marking, biggest step 0.3 ms) [GC interrupt] [GC in old space requested].
...
V8也提供了很多程序启动选项:
选项 | 含义 |
---|---|
–max-stack-size | 设置栈大小 |
–v8-options | 打印V8相关命令 |
–trace-bailout | 查找不能被优化的函数,重写 |
–trace-deopt | 查找不能优化的函数 |
这些选项可以让我们查看V8在执行时的各种log日志,对于排查隐晦问题比较有用。
然而这堆日志看起来很糟糕,可以先将日志输出来之后交给专业的工具帮我们分析,相信大部分人都用Chrome DevTools的JavaScript CPU Profile:
通过Profile工具可以找到具体函数在整个程序中的执行时间和执行时间占比,从而分析到具体的代码问题,V8 也提供了Profile日志导出:
node --prof test.js
执行命令之后,会在该目录下产生一个 *-v8.log 的日志文件,我们可以安装一个日志分析工具 tick:
➜ $ sudo npm install tick -g |
也可以使用headdump之类的工具将日志导出,然后放到Chrome的Profile中去分析,如果看过深入浅出node.js的童鞋可以参考内存调优的章节。
总结
对于任何一门程序而已,内存调优都是比较难的,这方面是需要智慧和经验,然而并不要被困难吓到,不断去折腾,相信每一次都会有新的收获。
参考:
https://nodejs.org
https://iojs.org
http://www.barretlee.com/blog/2015/10/07/debug-nodejs-in-command-line/