这是针对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。