How to make the node.js application run stably

Node.js程序在越来越多的公司部署在生产环境,程序的稳定运行逐渐被大家所关注。下面我就分享一下这方面的经验,仅供参考。

一、捕获错误

try 语句

try语句捕获错误是Javascript在网页或服务器上最常见也是最简单的捕获错误方式。

1
2
3
4
5
6
7
8
9
try {
// 业务代码
}
catch(e) {
// 异常时执行的代码
}
finally {
// 最后执行的代码
}

这种方式适用于当前执行的代码,语法错误,变量使用异常等情况的异常捕获。但是对于函数、对象里的异常,是无法捕获的。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var testVar;
try {
var testFun = function () {
console.log(testVar.toString());
}
}
catch(e) {
console.log("run catch", e);
}
finally {
console.log("run finally");
}
testFun();

如果你们觉得会运行“console.log(testVar.toString())”时捕获到变量未初始化,那就错了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
yuenshui@yuenshuideMacBook-Pro ~/Documents/website/nodejs> node test001.js
run finally
/Users/yuenshui/Documents/website/nodejs/test001.js:9
console.log(testVar.toString());
^
TypeError: Cannot read property 'toString' of undefined
at testFun (/Users/yuenshui/Documents/website/nodejs/test001.js:9:29)
at Object.<anonymous> (/Users/yuenshui/Documents/website/nodejs/test001.js:19:1)
at Module._compile (module.js:409:26)
at Object.Module._extensions..js (module.js:416:10)
at Module.load (module.js:343:32)
at Function.Module._load (module.js:300:12)
at Function.Module.runMain (module.js:441:10)
at startup (node.js:139:18)
at node.js:968:3

程序执行到这里就会闪退(Crash)。

解决办法是,在执行的地方捕获错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var testVar;
var testFun = function () {
console.log(testVar.toString());
}
try {
testFun();
}
catch(e) {
console.log("run catch", e);
}
finally {
console.log("run finally");
}

运行结果:

1
2
3
yuenshui@yuenshuideMacBook-Pro ~/Documents/website/nodejs> node test001.js
run catch [TypeError: Cannot read property 'toString' of undefined]
run finally

类型判断

如上例,只要在使用变量前加上类型判断,就不会出现程序异常。

1
2
3
4
5
6
7
8
9
var testVar;
var testFun = function () {
if(typeof testVar != 'undefined') {
console.log(testVar.toString());
}
}
testFun();

像这样的情况,一般的熟手都能做的很好,不用依赖try捕获异常来保证稳定运行。

Promise

如果认为上面两个例子通过判断类型避免异常和try语句捕获异常就能解决异常的出现,那就错误。因为还有异步的存在(^_^ 天刹的异步),异步执行的代码中如果产生异常,调用阶段使用try是没意义的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var Promise = require("bluebird");
var fs = require("fs");
var testFun = function(callback) {
fs.readFile("./abc.bin", function (err, data) {
if(err) throw err;
});
};
try {
testFun(function(data) {
console.log(data);
});
}
catch(e) {
console.log("run catch", e);
}
finally {
console.log("run finally");
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
yuenshui@yuenshuideMacBook-Pro ~/Documents/website/nodejs> node test013.js
module.js:327
throw err;
^
Error: Cannot find module '/Users/yuenshui/Documents/website/nodejs/test013.js'
at Function.Module._resolveFilename (module.js:325:15)
at Function.Module._load (module.js:276:25)
at Function.Module.runMain (module.js:441:10)
at startup (node.js:139:18)
at node.js:968:3

异步下可以使用Promise捕获:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var Promise = require("bluebird");
var fs = require("fs");
var testFun = function() {
return new Promise(function(resolve, reject) {
fs.readFile("./test.txt", function (err, data) {
if(err) reject(err);
else {
resolve(data);
}
});
});
};
testFun()
.then(function (data) {
console.log("then:", data.toString());
})
.catch(function (err) {
console.log("catch:", err);
});

运行结果:

1
2
yuenshui@yuenshuideMacBook-Pro ~/Documents/website/nodejs/chat> node test015.js
catch: { [Error: ENOENT: no such file or directory, open './test.txt'] errno: -2, code: 'ENOENT', syscall: 'open', path: './test.txt' }

使用domain

这是官方给的一个例子,在用domain创建的域里执行程序,将会捕获到程序的错误。
官方给的解释是:domain提供了将多个不同IO操作作为单个组处理的方式。如果任何注册到域的事件发射器或回调函数发出“error”事件或者抛出一个错误,那么将通知域对象,而不是直接让这个错误的上下文从process.on(‘uncaughtException’)处理程序中丢失掉,甚至程序立即退出并显示错误代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
var d = require('domain').create();
d.on('error', (er) => {
// The error won't crash the process, but what it does is worse!
// Though we've prevented abrupt process restarting, we are leaking
// resources like crazy if this ever happens.
// This is no better than process.on('uncaughtException')!
console.log('error, but oh well', er.message);
});
d.run(() => {
require('http').createServer((req, res) => {
handleRequest(req, res);
}).listen(PORT);
});

uncaughtException 事件

通过process的uncaughtException事件,来处理未捕获的异常。

1
2
3
process.on('uncaughtException', function (err) {
console.log('catch fatal error: ' + err.stack || err);
});

二、进程守护

pm2

官网:http://pm2.keymetrics.io/
PM2是一个进程管理器,在进程闪退后立即重启该进程,起到了守护作用,并且记录error_log,用于错误分析。
安装

1
npm install -g pm2

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ npm install pm2 -g # 命令行安装 pm2
$ pm2 start app.js -i 4 #后台运行pm2,启动4个app.js
# 也可以把'max' 参数传递给 start
# 正确的进程数目依赖于Cpu的核心数目
$ pm2 start app.js --name my-api # 命名进程
$ pm2 list # 显示所有进程状态
$ pm2 monit # 监视所有进程
$ pm2 logs # 显示所有进程日志
$ pm2 stop all # 停止所有进程
$ pm2 restart all # 重启所有进程
$ pm2 reload all # 0秒停机重载进程 (用于 NETWORKED 进程)
$ pm2 stop 0 # 停止指定的进程
$ pm2 restart 0 # 重启指定的进程
$ pm2 startup # 产生 init 脚本 保持进程活着
$ pm2 web # 运行健壮的 computer API endpoint (http://localhost:9615)
$ pm2 delete 0 # 杀死指定的进程
$ pm2 delete all # 杀死全部进程
运行进程的不同方式:
$ pm2 start app.js -i max # 根据有效CPU数目启动最大进程数目
$ pm2 start app.js -i 3 # 启动3个进程
$ pm2 start app.js -x #用fork模式启动 app.js 而不是使用 cluster
$ pm2 start app.js -x -- -a 23 # 用fork模式启动 app.js 并且传递参数 (-a 23)
$ pm2 start app.js --name serverone # 启动一个进程并把它命名为 serverone
$ pm2 stop serverone # 停止 serverone 进程
$ pm2 start app.json # 启动进程, 在 app.json里设置选项
$ pm2 start app.js -i max -- -a 23 #在--之后给 app.js 传递参数
$ pm2 start app.js -i max -e err.log -o out.log # 启动 并 生成一个配置文件
你也可以执行用其他语言编写的app ( fork 模式):
$ pm2 start my-bash-script.sh -x --interpreter bash
$ pm2 start my-python-script.py -x --interpreter python

forever

官网:https://github.com/foreverjs/forever
从功能上来看,个人感觉不如PM2全面和完善,具体的网上有很多教程和资料,就不赘述了。

调试分析

node-inspector

这个模块提供了基于V8分析器的调试接口,配合chrome浏览器,可以直观的浏览运行时内存。
用于分析业务中可能的缺陷很有帮助。
安装并运行node-inspector

1
2
3
4
> node --debug app.js
> node-inspector
Node Inspector v0.12.8
Visit http://127.0.0.1:8080/?port=5858 to start debugging.

运行需要调试的项目,加“—-debug”参数

1
2
> node —-debug app.js
Debugger listening on [::]:5858

chrome 打开 http://127.0.0.1:8080/?port=5858 就可以看到调试界面。
点击“Profiles”,选择“Take Heap Snapshor” 点击 “Take Snapshot”就可以浏览运行时内存数据了。
Node.js Take Snapshot

node-memwatch

node-mtrace

node-heap-dump

注意:本站博文均系原创,欢迎转载,请注明出处和原网址