這篇文章給大家介紹Node.js 中domain模塊如何使用,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

創新互聯2013年至今,是專業互聯網技術服務公司,擁有項目做網站、網站制作網站策劃,項目實施與項目整合能力。我們以讓每一個夢想脫穎而出為使命,1280元讓胡路做網站,已為上家服務,為讓胡路各地企業和個人服務,聯系電話:18982081108
異步異常處理
異步異常的特點
由于node的回調異步特性,無法通過try catch來捕捉所有的異常:
try {
process.nextTick(function () {
foo.bar();
});
} catch (err) {
//can not catch it
}而對于web服務而言,其實是非常希望這樣的:
//express風格的路由
app.get('/index', function (req, res) {
try {
//業務邏輯
} catch (err) {
logger.error(err);
res.statusCode = 500;
return res.json({success: false, message: '服務器異常'});
}
});如果try catch能夠捕獲所有的異常,這樣我們可以在代碼出現一些非預期的錯誤時,能夠記錄下錯誤的同時,友好的給調用者返回一個500錯誤。可惜,try catch無法捕獲異步中的異常。所以我們能做的只能是:
app.get('/index', function (req, res) {
// 業務邏輯
});
process.on('uncaughtException', function (err) {
logger.error(err);
});這個時候,雖然我們可以記錄下這個錯誤的日志,且進程也不會異常退出,但是我們是沒有辦法對發現錯誤的請求友好返回的,只能夠讓它超時返回。
domain
在node v0.8+版本的時候,發布了一個模塊domain。這個模塊做的就是try catch所無法做到的:捕捉異步回調中出現的異常。
于是乎,我們上面那個無奈的例子好像有了解決的方案:
var domain = require('domain');
//引入一個domain的中間件,將每一個請求都包裹在一個獨立的domain中
//domain來處理異常
app.use(function (req,res, next) {
var d = domain.create();
//監聽domain的錯誤事件
d.on('error', function (err) {
logger.error(err);
res.statusCode = 500;
res.json({sucess:false, messag: '服務器異常'});
d.dispose();
});
d.add(req);
d.add(res);
d.run(next);
});
app.get('/index', function (req, res) {
//處理業務
});我們通過中間件的形式,引入domain來處理異步中的異常。當然,domain雖然捕捉到了異常,但是還是由于異常而導致的堆棧丟失會導致內存泄漏,所以出現這種情況的時候還是需要重啟這個進程的,有興趣的同學可以去看看domain-middleware這個domain中間件。
詭異的失效
我們的測試一切正常,當正式在生產環境中使用的時候,發現domain突然失效了!它竟然沒有捕獲到異步中的異常,最終導致進程異常退出。經過一番排查,最后發現是由于引入了redis來存放session導致的。
var http = require('http');
var connect = require('connect');
var RedisStore = require('connect-redis')(connect);
var domainMiddleware = require('domain-middleware');
var server = http.createServer();
var app = connect();
app.use(connect.session({
key: 'key',
secret: 'secret',
store: new RedisStore(6379, 'localhost')
}));
//domainMiddleware的使用可以看前面的鏈接
app.use(domainMiddleware({
server: server,
killTimeout: 30000
}));此時,當我們的業務邏輯代碼中出現了異常,發現竟然沒有被domain捕獲!經過一番嘗試,終于將問題定位到了:
var domain = require('domain');
var redis = require('redis');
var cache = redis.createClient(6379, 'localhost');
function error() {
cache.get('a', function () {
throw new Error('something wrong');
});
}
function ok () {
setTimeout(function () {
throw new Error('something wrong');
}, 100);
}
var d = domain.create();
d.on('error', function (err) {
console.log(err);
});
d.run(ok); //domain捕獲到異常
d.run(error); //異常被拋出奇怪了!都是異步調用,為什么前者被捕獲,后者卻沒辦法捕獲到呢?
Domain剖析
回過頭來,我們來看看domain做了些什么來讓我們捕獲異步的請求(代碼來自node v0.10.4,此部分可能正在快速變更優化)。
node事件循環機制
在看Domain的原理之前,我們先要了解一下nextTick和_tickCallback的兩個方法。
function laterCall() {
console.log('print me later');
}
process.nextTick(laterCallback);
console.log('print me first');上面這段代碼寫過node的人都很熟悉,nextTick的作用就是把laterCallback放到下一個事件循環去執行。而_tickCallback方法則是一個非公開的方法,這個方法是在當前時間循環結束之后,調用之以繼續進行下一個事件循環的入口函數。
換而言之,node為事件循環維持了一個隊列,nextTick入隊,_tickCallback出列。
domain的實現
在了解了node的事件循環機制之后,我們再來看看domain做了些什么。
domain自身其實是一個EventEmitter對象,它通過事件的方式來傳遞捕獲的錯誤。這樣我們在研究它的時候,就簡化到兩個點:
什么時候觸發domain的error事件:
進程拋出了異常,沒有被任何的try catch捕獲到,這時候將會觸發整個process的processFatal,此時如果在domain包裹之中,將會在domain上觸發error事件,反之,將會在process上觸發uncaughtException事件。
domain如何在多個不同的事件循環中傳遞:
當domain被實例化之后,我們通常會調用它的run方法(如之前在web服務中的使用),來將某個函數在這個domain示例的包裹中執行。被包裹的函數在執行的時候,process.domain這個全局變量將會被指向這個domain實例。當這個事件循環中,拋出異常調用processFatal的時候,發現process.domain存在,就會在domain上觸發error事件。
在require引入domain模塊之后,會重寫全局的nextTick和_tickCallback,注入一些domain相關的代碼:
//簡化后的domain傳遞部分代碼
function nextDomainTick(callback) {
nextTickQueue.push({callback: callback, domain: process.domain});
}
function _tickDomainCallback() {
var tock = nextTickQueue.pop();
//設置process.domain = tock.domain
tock.domain && tock.domain.enter();
callback();
//清除process.domain
tock.domain && tock.domain.exit();
}
};這個是其在多個事件循環中傳遞domain的關鍵:nextTick入隊的時候,記錄下當前的domain,當這個被加入隊列中的事件循環被_tickCallback啟動執行的時候,將新的事件循環的process.domain置為之前記錄的domain。這樣,在被domain所包裹的代碼中,不管如何調用process.nextTick, domain將會一直被傳遞下去。
當然,node的異步還有兩種情況,一種是event形式。因此在EventEmitter的構造函數有如下代碼:
if (exports.usingDomains) {
// if there is an active domain, then attach to it.
domain = domain || require('domain');
if (domain.active && !(this instanceof domain.Domain)) {
this.domain = domain.active;
}
}實例化EventEmitter的時候,將會把這個對象和當前的domain綁定,當通過emit觸發這個對象上的事件時,像_tickCallback執行的時候一樣,回調函數將會重新被當前的domain包裹住。
而另一種情況,是setTimeout和setInterval,同樣的,在timer的源碼中,我們也可以發現這樣的一句代碼:
if (process.domain) timer.domain = process.domain;
跟EventEmmiter一樣,之后這些timer的回調函數也將被當前的domain包裹住了。
node通過在nextTick, timer, event三個關鍵的地方插入domain的代碼,讓它們得以在不同的事件循環中傳遞。
更復雜的domain
有些情況下,我們可能會遇到需要更加復雜的domain使用。
domain嵌套:我們可能會外層有domain的情況下,內層還有其他的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);
});為了實現這個功能,其實domain還會偷偷的自己維持一個domain的stack,有興趣的童鞋可以在這里看到。
回頭解決疑惑
回過頭來,我們再來看剛才遇到的問題:為什么兩個看上去都是同樣的異步調用,卻有一個domain無法捕獲到異常?理解了原理之后不難想到,肯定是調用了redis的那個異步調用在拋出錯誤的這個事件循環內,是不在domain的范圍之內的。我們通過一段更加簡短的代碼來看看,到底在哪里出的問題。
var domain = require('domain');
var EventEmitter = require('events').EventEmitter;
var e = new EventEmitter();
var timer = setTimeout(function () {
e.emit('data');
}, 10);
function next() {
e.once('data', function () {
throw new Error('something wrong here');
});
}
var d = domain.create();
d.on('error', function () {
console.log('cache by domain');
});
d.run(next);此時我們同樣發現,錯誤不會被domain捕捉到,原因很清晰了:timer和e兩個關鍵的對象在初始化的時候都時沒有在domain的范圍之內,因此,當在next函數中監聽的事件被觸發,執行拋出異常的回調函數時,其實根本就沒有處于domain的包裹中,當然就不會被domain捕獲到異常了!
其實node針對這種情況,專門設計了一個API:domain.add。它可以將domain之外的timer和event對象,添加到當前domain中去。對于上面那個例子:
d.add(timer); //or d.add(e);
將timer或者e任意一個對象添加到domain上,就可以讓錯誤被domain捕獲了。
再來看最開始redis導致domain無法捕捉到異常的問題。我們是不是也有辦法可以解決呢?
其實對于這種情況,還是沒有辦法實現最佳的解決方案的。現在對于非預期的異常產生的時候,我們只能夠讓當前請求超時,然后讓這個進程停止服務,之后重新啟動。graceful模塊配合cluster就可以實現這個解決方案。
__domain十分強大,但不是萬能的。__希望在看過這篇文章之后,大家能夠正確的使用domian,避免踩坑。
關于Node.js 中domain模塊如何使用就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
當前文章:Node.js中domain模塊如何使用
文章地址:http://www.yijiale78.com/article14/gddgde.html
成都網站建設公司_創新互聯,為您提供商城網站、用戶體驗、品牌網站建設、外貿網站建設、微信小程序、網站營銷
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯