穩(wěn)定性: 2 - 不穩(wěn)定
Node.js域包含了能把不同的IO操作看成單獨(dú)組的方法。如果任何一個(gè)注冊(cè)到域的事件或者回調(diào)觸發(fā)error
事件,或者拋出一個(gè)異常,則域就會(huì)接收到通知,而不是在process.on('uncaughtException')
處理程序中丟失錯(cuò)誤的上下文,也不會(huì)使程序立即以錯(cuò)誤代碼退出。
你不能將域錯(cuò)誤處理程序看做錯(cuò)誤發(fā)生時(shí)就關(guān)閉進(jìn)程的一個(gè)替代方案。
根據(jù)JavaScript中拋出異常的工作原理,基本上沒有方法可以安全的“回到原先離開的位置”,在不泄露引用,或者不造成一些其他未定義的狀態(tài)下。
響應(yīng)拋出錯(cuò)誤最安全的方法就是關(guān)閉進(jìn)程。一個(gè)正常的服務(wù)器會(huì)可能有很多活躍的連接,因?yàn)槟硞€(gè)錯(cuò)誤就關(guān)閉所有連接顯然是不合理的。
比較好的方法是給觸發(fā)錯(cuò)誤的請(qǐng)求發(fā)送錯(cuò)誤響應(yīng),讓其他連接正常工作時(shí),停止監(jiān)聽觸發(fā)錯(cuò)誤的人的新請(qǐng)求。
按這種方法,域
和集群(cluster)模塊可以協(xié)同工作,當(dāng)某個(gè)進(jìn)程遇到錯(cuò)誤時(shí),主進(jìn)程可以復(fù)制一個(gè)新的進(jìn)程。對(duì)于Node程序,終端代理或者注冊(cè)的服務(wù),可以留意錯(cuò)誤并做出反應(yīng)。
舉例來(lái)說(shuō),下面的代碼就不是好辦法:
javascript
// XXX WARNING! BAD IDEA!
var d = require('domain').create();
d.on('error', function(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(function() {
require('http').createServer(function(req, res) {
handleRequest(req, res);
}).listen(PORT);
});
通過使用域的上下文,并將程序切為多個(gè)工作進(jìn)程,我們能夠更合理的響應(yīng),處理錯(cuò)誤更安全:
javascript
// 好一些的做法!
var cluster = require('cluster');
var PORT = +process.env.PORT || 1337;
if (cluster.isMaster) {
// In real life, you'd probably use more than just 2 workers,
// and perhaps not put the master and worker in the same file.
//
// You can also of course get a bit fancier about logging, and
// implement whatever custom logic you need to prevent DoS
// attacks and other bad behavior.
//
// See the options in the cluster documentation.
//
// The important thing is that the master does very little,
// increasing our resilience to unexpected errors.
cluster.fork();
cluster.fork();
cluster.on('disconnect', function(worker) {
console.error('disconnect!');
cluster.fork();
});
} else {
// the worker
//
// This is where we put our bugs!
var domain = require('domain');
// See the cluster documentation for more details about using
// worker processes to serve requests. How it works, caveats, etc.
var server = require('http').createServer(function(req, res) {
var d = domain.create();
d.on('error', function(er) {
console.error('error', er.stack);
// Note: we're in dangerous territory!
// By definition, something unexpected occurred,
// which we probably didn't want.
// Anything can happen now! Be very careful!
try {
// make sure we close down within 30 seconds
var killtimer = setTimeout(function() {
process.exit(1);
}, 30000);
// But don't keep the process open just for that!
killtimer.unref();
// stop taking new requests.
server.close();
// Let the master know we're dead. This will trigger a
// 'disconnect' in the cluster master, and then it will fork
// a new worker.
cluster.worker.disconnect();
// try to send an error to the request that triggered the problem
res.statusCode = 500;
res.setHeader('content-type', 'text/plain');
res.end('Oops, there was a problem!\n');
} catch (er2) {
// oh well, not much we can do at this point.
console.error('Error sending 500!', er2.stack);
}
});
// Because req and res were created before this domain existed,
// we need to explicitly add them.
// See the explanation of implicit vs explicit binding below.
d.add(req);
d.add(res);
// Now run the handler function in the domain.
d.run(function() {
handleRequest(req, res);
});
});
server.listen(PORT);
}
// This part isn't important. Just an example routing thing.
// You'd put your fancy application logic here.
function handleRequest(req, res) {
switch(req.url) {
case '/error':
// We do some async stuff, and then...
setTimeout(function() {
// Whoops!
flerb.bark();
});
break;
default:
res.end('ok');
}
}
任何時(shí)候一個(gè)錯(cuò)誤被路由傳到一個(gè)域的時(shí),會(huì)添加幾個(gè)字段。
error.domain
第一個(gè)處理錯(cuò)誤的域error.domainEmitter
用這個(gè)錯(cuò)誤對(duì)象觸發(fā)'error'事件的事件分發(fā)器error.domainBound
綁定到domain的回調(diào)函數(shù),第一個(gè)參數(shù)是error。error.domainThrown
boolean值,表明是拋出錯(cuò)誤,分發(fā),或者傳遞給綁定的回到函數(shù)。 新分發(fā)的對(duì)象(包括流對(duì)象(Stream objects),請(qǐng)求(requests),響應(yīng)(responses)等)會(huì)隱式的綁定到當(dāng)前正在使用的域中。
另外,傳遞給底層事件循環(huán)(比如fs.open或其他接收回調(diào)的方法)的回調(diào)函數(shù)將會(huì)自動(dòng)的綁定到這個(gè)域。如果他們拋出異常,域會(huì)捕捉到錯(cuò)誤信息。
為了避免過度使用內(nèi)存,域?qū)ο蟛粫?huì)象隱式的添加為有效域的子對(duì)象。如果這樣做的話,很容易影響到請(qǐng)求和響應(yīng)對(duì)象的垃圾回收。
如果你想將域?qū)ο笞鳛樽訉?duì)象嵌入到父域里,就必須顯式的添加它們。
隱式綁定路由拋出的錯(cuò)誤和'error'
事件,但是不會(huì)注冊(cè)事件分發(fā)器到域,所以domain.dispose()
不會(huì)關(guān)閉事件分發(fā)器。隱式綁定僅需注意拋出的錯(cuò)誤和 'error'
事件。
有時(shí)候正在使用的域并不是某個(gè)事件分發(fā)器的域。或者說(shuō),事件分發(fā)器可能在某個(gè)域里創(chuàng)建,但是被綁定到另外一個(gè)域里。
例如,HTTP服務(wù)器使用正一個(gè)域?qū)ο?,但我們希望可以每一個(gè)請(qǐng)求使用一個(gè)不同的域。
這可以通過顯式綁定來(lái)實(shí)現(xiàn)。
例如:
// 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);
});
用于返回一個(gè)新的域?qū)ο蟆?/p>
這個(gè)類封裝了將錯(cuò)誤和沒有捕捉到的異常到有效對(duì)象功能。
域是EventEmitter的子類. 監(jiān)聽它的error
事件來(lái)處理捕捉到的錯(cuò)誤。
fn
{Function}在域的上下文運(yùn)行提供的函數(shù),隱式的綁定了所有的事件分發(fā)器,計(jì)時(shí)器和底層請(qǐng)求。
這是使用域的基本方法。
例如:
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);
});
});
這個(gè)例子里程序不會(huì)崩潰,而會(huì)觸發(fā)d.on('error')
。
顯式添加到域里的計(jì)時(shí)器和事件分發(fā)器數(shù)組。
emitter
{EventEmitter | Timer} 添加到域里的計(jì)時(shí)器和事件分發(fā)器顯式地將一個(gè)分發(fā)器添加到域。如果分發(fā)器調(diào)用的事件處理函數(shù)拋出錯(cuò)誤,或者分發(fā)器遇到error
事件,將會(huì)導(dǎo)向域的error
事件,和隱式綁定一樣。
對(duì)于setInterval
和setTimeout
返回的計(jì)時(shí)器同樣適用。如果這些回調(diào)函數(shù)拋出錯(cuò)誤,將會(huì)被域的'error'處理器捕捉到。
如果計(jì)時(shí)器或分發(fā)器已經(jīng)綁定到域,那它將會(huì)從上一個(gè)域移除,綁定到當(dāng)前域。
emitter
{EventEmitter | Timer} 要移除的分發(fā)器或計(jì)時(shí)器與domain.add(emitter)函數(shù)恰恰相反,這個(gè)函數(shù)將分發(fā)器移除出域。
callback
{Function} 回調(diào)函數(shù)返回的函數(shù)是一個(gè)對(duì)于所提供的回調(diào)函數(shù)的包裝函數(shù)。當(dāng)調(diào)用這個(gè)返回的函數(shù)被時(shí),所有被拋出的錯(cuò)誤都會(huì)被導(dǎo)向到這個(gè)域的error
事件。
var d = domain.create();
function readSomeFile(filename, cb) {
fs.readFile(filename, 'utf8', d.bind(function(er, data) {
// if this throws, it will also be passed to the domain
return cb(er, data ? JSON.parse(data) : null);
}));
}
d.on('error', function(er) {
// an error occurred somewhere.
// if we throw it now, it will crash the program
// with the normal line number and stack message.
});
callback
{Function} 回調(diào)函數(shù)和domain.bind(callback)
類似。除了捕捉被拋出的錯(cuò)誤外,它還會(huì)攔截Error對(duì)象作為參數(shù)傳遞到這個(gè)函數(shù)。
這種方式下,常見的if (er) return callback(er);
模式,能被一個(gè)地方一個(gè)錯(cuò)誤處理替換。
var d = domain.create();
function readSomeFile(filename, cb) {
fs.readFile(filename, 'utf8', d.intercept(function(data) {
// note, the first argument is never passed to the
// callback since it is assumed to be the 'Error' argument
// and thus intercepted by the domain.
// if this throws, it will also be passed to the domain
// so the error-handling logic can be moved to the 'error'
// event on the domain instead of being repeated throughout
// the program.
return cb(null, JSON.parse(data));
}));
}
d.on('error', function(er) {
// an error occurred somewhere.
// if we throw it now, it will crash the program
// with the normal line number and stack message.
});
這個(gè)函數(shù)就像run
,bind
和intercept
的管道系統(tǒng),它設(shè)置有效域。它設(shè)定了域的domain.active
和process.domain
,還隱式的將域推到域模塊管理的域棧(關(guān)于域棧的細(xì)節(jié)詳見domain.exit()
)。enter函數(shù)的調(diào)用,分隔了異步調(diào)用鏈以及綁定到一個(gè)域的I/O操作的結(jié)束或中斷。
調(diào)用enter
僅改變活動(dòng)的域,而不改變域本身。在一個(gè)單獨(dú)的域里可以調(diào)用任意多次Enter
和exit
。
exit
函數(shù)退出當(dāng)前域,并從域的棧里移除。每當(dāng)程序的執(zhí)行流程要切換到不同的異步調(diào)用鏈的時(shí)候,要保證退出當(dāng)前域。調(diào)用exit函數(shù),分隔了異步調(diào)用鏈,和綁定到一個(gè)域的I/O操作的結(jié)束或中斷。
如果有多個(gè)嵌套的域綁定到當(dāng)前的上下文,exit
函數(shù)將會(huì)退出所有嵌套。
調(diào)用exit
僅改變活躍域,不會(huì)改變自身域。在一個(gè)單獨(dú)的域里可以調(diào)用任意多次Enter
和exit
。
如果在這個(gè)域名下exit已經(jīng)被設(shè)置,exit將不退出域返回。
穩(wěn)定性: 0 - 拋棄。通過域里設(shè)置的錯(cuò)誤事件來(lái)顯示的消除失敗的 IO 操作。
調(diào)用dispos
后,通過run,bind或intercept綁定到域的回調(diào)函數(shù)不再使用這個(gè)域,并且分發(fā)dispose
事件。
更多建議: