Node.js 应用里面,常见性能问题从表现上来看有这么几类(Node.js 性能平台都提供了对应解决方案):
- CPU 飚高:做 CPU Prfiling 定位热点函数
- 内存泄露:堆快照/heaptimeline/heapprofile进行定位
- CPU/内存使用都不高,但是 QPS 上不去或者 RT 很长:trace进行定位
CPU 飚高的情况又可分为两类:
- 仍然可以继续处理业务,只是 RT 变长,这可以通过 CPU Profiling 定位
- 完全无法继续处理业务,表现为 CPU 100% 占用,堆信息监控为直线
无法处理新业务的情况,JS 代码一般处于下面几种状态:
- while/for 循环一直运行
- 计算一直无法完成:例如执行一个斐波那契数列运算,当输入大于
50
的时候 - 执行长时间正则表达式,
Node.js 性能平台在运行时v3.11.3
和v4.2.1
及以前的版本里面对于,由于无法启动 CPU Profiling,也没有特别好的定位方法。只能根据监控提供问题开始时刻,去 nginx 的 access 日志里面查找相关线索。
随着 v3.11.4
和 v4.2.1
版本的发布,这两大难题将不会继续困扰我们。
即使代码处于上述中死循环
,长时间计算
或者长时间正则表达式匹配
状态,仍然可以启动 CPU Profiling,定位到问题所在。
下面的例子都来自不同行业的用户案例,为了简化验证过程,集成到了一个简单的 http 服务中。
默认用户已经开通 Node.js 性能服务,否则请参考 用户指南
'use strict';
const http = require('http');
function regexp() {
let str = '<br/> ' +
' 早餐后自由活动,于指定时间集合自行办理退房手续。';
str += '<br/> <br/>' +
' <br/> ' +
' <br/>';
str += ' <br/>' +
' ' +
' ' +
' <br/>';
str += ' <br/> <br/>';
str += ' ' +
' ' +
' 根据船班时间,自行前往暹粒机场,返回中国。<br/>';
str += '如需送机服务,需增加280/每单。<br/>';
let r = String(str).replace(/(^(\s*?<br[\s\/]*?>\*?)+|(\s*?<br[\s\/]*?>\s*?)+?$)/igm, '');
}
function deadloop () {
while(1);
}
function f1() {
deadloop();
}
function f2(){
f1();
}
function f3(){
f2();
}
function f4(){
f3();
}
http.createServer( (req, res) => {
console.log(req.url);
switch(req.url) {
case '/regexp': {
regexp();
res.end('regexp case');
} break;
case '/deadloop': {
f4();
res.end('deadloop case');
} break;
case '/exit': {
process.exit(0);
} break;
default: {
res.writeHead(200, "OK",{'Content-Type': 'text/html'});
res.write('<html><head><title>Node.js</title></head><body style="font-family:arial;">');
res.write('<h2> Regular Expression and Dead Loop Detection Example</h2>');
res.write('<p>Click on button below to trigger long time running regexp test.');
res.write('<form enctype="application/x-www-form-urlencoded" action="/regexp" method="post">');
res.write('<button>Trigger Long Running Regular Expression</button></form>');
res.write('<p>Click on button below to enter JavaScript dead loop test');
res.write('<form enctype="application/x-www-form-urlencoded" action="/deadloop" method="post">');
res.write('<button>Trigger Dead Loop JS function</button></form>');
res.write('<p>The test can be terminated only before the above cases triggered.');
res.write('<form enctype="application/x-www-form-urlencoded" action="/exit" method="post">');
res.write('<button>Exit Test</button></form>');
res.write('</form></body></html');
res.end();
} break;
}
}).listen(8080);
console.log('\nhttp listen at 8080 pid: ', process.pid);
以下两幅图均是代码已经处于 CPU 100% 状态后启动 Profiling 后定位结果。
- 正则表达式准确定位到当前执行的函数
- 死循环目前定位到
while(1)
所在函数的调用者。如果在 CPU 100% 前启动 Profiling 则可以定位到具体函数,然而线上故障的不可预测性使得我们无法在问题出现前3分钟启动,后续 Node.js 性能平台会继续优化此处。
注意:
v3.11.4 和 v4.2.1 运行时采用了 SIGUSR2 触发诊断。如果用户没有设置 ENABLE_NODE_LOG=YES,请不要点击故障诊断按钮,否则会造成进程 crash。 其它版本没有这个问题。