Node.js实现100%在线的要则

    
banq 14-01-12



这是针对NodeJS的生产环境中如何保证100%在线不停机的文章。


1.使用Domain模块处理未捕获的异常

未捕获的异常发生时,错误被抛出一个try/ catch块以外,或者一个错误事件发出后,并没有谁去侦听它。NodeJS对待未捕获异常的默认操作是退出崩溃的进程,如果这个进程是一个服务器,就导致服务器停机,所以我们要尽可能避免未捕获的异常,并且在它们发生时明智地处理它们。

使用Domain编写坚固的代码和测试来完成这个一目的。NodeJS的Domain模块.

Domain的方法有:
add: 绑定一个发射器到domain.
run: 在domain场景下运行
bind: 绑定一个函数
intercept: 类似绑定但是不处理第一个参数错误
dispose: 取消IO 和 timers.

使用Domain的案例:

// create a top-level domain for the server
var serverDomain = domain.create();

serverDomain.run(function() {
// server is created in the scope of serverDomain
http.createServer(function(req, res) {
// req and res are also created in the scope of serverDomain
// however, we'd prefer to have a separate domain for each request.
// create it first thing, and add req and res to it.
var reqd = domain.create();
reqd.add(req);
reqd.add(res);
reqd.on('error', function(er) {
console.error('Error', er, req.url);
try {
res.writeHead(500);
res.end('Error occurred, sorry.');
} catch (er) {
console.error('Error sending 500', er, req.url);
}
});
}).listen(1337);
});


运行一个普通函数的案例:


var d = domain.create();
d.on('error', function(er) {
console.error('Caught error!', er);
});
d.run(function() {
process.nextTick(function() {
setTimeout(function() { // simulating some various async stuff
fs.open('non-existent file', 'r', function(er, fd) {
if (er) throw er;
// proceed...
});
}, 100);
});
});


domain能够捕获异步错误:


var d = require('domain').create();

d.on('error', function (err) {
console.log("domain caught", err);
});

var f = d.bind(function() {
console.log(domain.active === d);
// <-- true
throw new Error(
"uh-oh");
});

setTimeout(f, 100);

上述f函数如果不绑定在domain场景下运行,其抛出的错误会使NodeJS崩溃,这里domain的错误处理器被调用,给你一个机会采取行动,比如重试忽略等。

当代码在Domain中运行时,domain是活动的,你可以通过domain.active 包含引用它,新的事件发射器将绑定到这个活动的domain。

2.使用cluster管理进程
domain并不足以完成持续在线不当机,有时一个独立的进程因为一些错误需要停止,使用NodeJS的Cluster模块,我们能分配一个主进程管理一个或多个工作进程,并且分布工作给它们,主进程需要不停止的一直运行,意味着socket总是能接受连接,当一个worker工作进程停止工作,主进程能替代它。

当我们初始化一个工作进程的加载好后,我们使用发送信号给主进程,可以给主进程分配Unix的符号链接symlink指向工作者代码,然后更新符号链接指向到一个新的代码版本,发送信号给主进程让它关闭一个旧的工作进程,开启新的工作进程,新的工作进程将用新的代码版本运行。

主进程和工作进程的协调通讯很重要,工作进程使用它们状态进行通讯,主进程必须知道基于这些状态采取何种行动,recluster是对node的原生cluster模块一个的包装,能提供工作进程的状态管理。

当工作进程需要关闭时,它们必须优雅地关闭,因为它们服务于很多请求,有大量的tcp连接,为了关闭这些存在的连接,我们必须增加中间件来检查应用是否可以进入一个正在关闭的状态,如果是就立即关闭。

var afterErrorHook = function(err) {
server.close(); // <-- ensure no new connections
}
调用server.close能确保没有新的连接

下面是关闭一个正在活动的进程:

var afterErrorHook = function(err) { // <-- called after an unrecoverable error
app.set(
"isShuttingDown", true); // <-- set state
server.close(function() {
process.exit(1);
// <-- all clear to exit
});
}

var shutdownMiddle = function(req, res, next) {
if(app.get(
"isShuttingDown") { // <-- check state
req.connection.setTimeout(1);
// <-- kill keep-alive
}
next();
}

我们可以在一个计时器timer中调用process.exit。

2