這篇文章給大家介紹Node.js 中domain模塊如何使用,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。
創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供澧縣網(wǎng)站建設(shè)、澧縣做網(wǎng)站、澧縣網(wǎng)站設(shè)計(jì)、澧縣網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)與制作、澧縣企業(yè)網(wǎng)站模板建站服務(wù),十年澧縣做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。異步異常處理
異步異常的特點(diǎn)
由于node的回調(diào)異步特性,無法通過try catch來捕捉所有的異常:
try { process.nextTick(function () { foo.bar(); }); } catch (err) { //can not catch it }
而對(duì)于web服務(wù)而言,其實(shí)是非常希望這樣的:
//express風(fēng)格的路由 app.get('/index', function (req, res) { try { //業(yè)務(wù)邏輯 } catch (err) { logger.error(err); res.statusCode = 500; return res.json({success: false, message: '服務(wù)器異常'}); } });
如果try catch能夠捕獲所有的異常,這樣我們可以在代碼出現(xiàn)一些非預(yù)期的錯(cuò)誤時(shí),能夠記錄下錯(cuò)誤的同時(shí),友好的給調(diào)用者返回一個(gè)500錯(cuò)誤??上?,try catch無法捕獲異步中的異常。所以我們能做的只能是:
app.get('/index', function (req, res) { // 業(yè)務(wù)邏輯 }); process.on('uncaughtException', function (err) { logger.error(err); });
這個(gè)時(shí)候,雖然我們可以記錄下這個(gè)錯(cuò)誤的日志,且進(jìn)程也不會(huì)異常退出,但是我們是沒有辦法對(duì)發(fā)現(xiàn)錯(cuò)誤的請(qǐng)求友好返回的,只能夠讓它超時(shí)返回。
domain
在node v0.8+版本的時(shí)候,發(fā)布了一個(gè)模塊domain。這個(gè)模塊做的就是try catch所無法做到的:捕捉異步回調(diào)中出現(xiàn)的異常。
于是乎,我們上面那個(gè)無奈的例子好像有了解決的方案:
var domain = require('domain'); //引入一個(gè)domain的中間件,將每一個(gè)請(qǐng)求都包裹在一個(gè)獨(dú)立的domain中 //domain來處理異常 app.use(function (req,res, next) { var d = domain.create(); //監(jiān)聽domain的錯(cuò)誤事件 d.on('error', function (err) { logger.error(err); res.statusCode = 500; res.json({sucess:false, messag: '服務(wù)器異常'}); d.dispose(); }); d.add(req); d.add(res); d.run(next); }); app.get('/index', function (req, res) { //處理業(yè)務(wù) });
我們通過中間件的形式,引入domain來處理異步中的異常。當(dāng)然,domain雖然捕捉到了異常,但是還是由于異常而導(dǎo)致的堆棧丟失會(huì)導(dǎo)致內(nèi)存泄漏,所以出現(xiàn)這種情況的時(shí)候還是需要重啟這個(gè)進(jìn)程的,有興趣的同學(xué)可以去看看domain-middleware這個(gè)domain中間件。
詭異的失效
我們的測試一切正常,當(dāng)正式在生產(chǎn)環(huán)境中使用的時(shí)候,發(fā)現(xiàn)domain突然失效了!它竟然沒有捕獲到異步中的異常,最終導(dǎo)致進(jìn)程異常退出。經(jīng)過一番排查,最后發(fā)現(xiàn)是由于引入了redis來存放session導(dǎo)致的。
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 }));
此時(shí),當(dāng)我們的業(yè)務(wù)邏輯代碼中出現(xiàn)了異常,發(fā)現(xiàn)竟然沒有被domain捕獲!經(jīng)過一番嘗試,終于將問題定位到了:
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); //異常被拋出
奇怪了!都是異步調(diào)用,為什么前者被捕獲,后者卻沒辦法捕獲到呢?
Domain剖析
回過頭來,我們來看看domain做了些什么來讓我們捕獲異步的請(qǐng)求(代碼來自node v0.10.4,此部分可能正在快速變更優(yōu)化)。
node事件循環(huán)機(jī)制
在看Domain的原理之前,我們先要了解一下nextTick和_tickCallback的兩個(gè)方法。
function laterCall() { console.log('print me later'); } process.nextTick(laterCallback); console.log('print me first');
上面這段代碼寫過node的人都很熟悉,nextTick的作用就是把laterCallback放到下一個(gè)事件循環(huán)去執(zhí)行。而_tickCallback方法則是一個(gè)非公開的方法,這個(gè)方法是在當(dāng)前時(shí)間循環(huán)結(jié)束之后,調(diào)用之以繼續(xù)進(jìn)行下一個(gè)事件循環(huán)的入口函數(shù)。
換而言之,node為事件循環(huán)維持了一個(gè)隊(duì)列,nextTick入隊(duì),_tickCallback出列。
domain的實(shí)現(xiàn)
在了解了node的事件循環(huán)機(jī)制之后,我們?cè)賮砜纯磀omain做了些什么。
domain自身其實(shí)是一個(gè)EventEmitter對(duì)象,它通過事件的方式來傳遞捕獲的錯(cuò)誤。這樣我們?cè)谘芯克臅r(shí)候,就簡化到兩個(gè)點(diǎn):
什么時(shí)候觸發(fā)domain的error事件:
進(jìn)程拋出了異常,沒有被任何的try catch捕獲到,這時(shí)候?qū)?huì)觸發(fā)整個(gè)process的processFatal,此時(shí)如果在domain包裹之中,將會(huì)在domain上觸發(fā)error事件,反之,將會(huì)在process上觸發(fā)uncaughtException事件。
domain如何在多個(gè)不同的事件循環(huán)中傳遞:
當(dāng)domain被實(shí)例化之后,我們通常會(huì)調(diào)用它的run方法(如之前在web服務(wù)中的使用),來將某個(gè)函數(shù)在這個(gè)domain示例的包裹中執(zhí)行。被包裹的函數(shù)在執(zhí)行的時(shí)候,process.domain這個(gè)全局變量將會(huì)被指向這個(gè)domain實(shí)例。當(dāng)這個(gè)事件循環(huán)中,拋出異常調(diào)用processFatal的時(shí)候,發(fā)現(xiàn)process.domain存在,就會(huì)在domain上觸發(fā)error事件。
在require引入domain模塊之后,會(huì)重寫全局的nextTick和_tickCallback,注入一些domain相關(guān)的代碼:
//簡化后的domain傳遞部分代碼 function nextDomainTick(callback) { nextTickQueue.push({callback: callback, domain: process.domain}); } function _tickDomainCallback() { var tock = nextTickQueue.pop(); //設(shè)置process.domain = tock.domain tock.domain && tock.domain.enter(); callback(); //清除process.domain tock.domain && tock.domain.exit(); } };
這個(gè)是其在多個(gè)事件循環(huán)中傳遞domain的關(guān)鍵:nextTick入隊(duì)的時(shí)候,記錄下當(dāng)前的domain,當(dāng)這個(gè)被加入隊(duì)列中的事件循環(huán)被_tickCallback啟動(dòng)執(zhí)行的時(shí)候,將新的事件循環(huán)的process.domain置為之前記錄的domain。這樣,在被domain所包裹的代碼中,不管如何調(diào)用process.nextTick, domain將會(huì)一直被傳遞下去。
當(dāng)然,node的異步還有兩種情況,一種是event形式。因此在EventEmitter的構(gòu)造函數(shù)有如下代碼:
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; } }
實(shí)例化EventEmitter的時(shí)候,將會(huì)把這個(gè)對(duì)象和當(dāng)前的domain綁定,當(dāng)通過emit觸發(fā)這個(gè)對(duì)象上的事件時(shí),像_tickCallback執(zhí)行的時(shí)候一樣,回調(diào)函數(shù)將會(huì)重新被當(dāng)前的domain包裹住。
而另一種情況,是setTimeout和setInterval,同樣的,在timer的源碼中,我們也可以發(fā)現(xiàn)這樣的一句代碼:
if (process.domain) timer.domain = process.domain;
跟EventEmmiter一樣,之后這些timer的回調(diào)函數(shù)也將被當(dāng)前的domain包裹住了。
node通過在nextTick, timer, event三個(gè)關(guān)鍵的地方插入domain的代碼,讓它們得以在不同的事件循環(huán)中傳遞。
更復(fù)雜的domain
有些情況下,我們可能會(huì)遇到需要更加復(fù)雜的domain使用。
domain嵌套:我們可能會(huì)外層有domain的情況下,內(nèi)層還有其他的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); });
為了實(shí)現(xiàn)這個(gè)功能,其實(shí)domain還會(huì)偷偷的自己維持一個(gè)domain的stack,有興趣的童鞋可以在這里看到。
回頭解決疑惑
回過頭來,我們?cè)賮砜磩偛庞龅降膯栴}:為什么兩個(gè)看上去都是同樣的異步調(diào)用,卻有一個(gè)domain無法捕獲到異常?理解了原理之后不難想到,肯定是調(diào)用了redis的那個(gè)異步調(diào)用在拋出錯(cuò)誤的這個(gè)事件循環(huán)內(nèi),是不在domain的范圍之內(nèi)的。我們通過一段更加簡短的代碼來看看,到底在哪里出的問題。
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);
此時(shí)我們同樣發(fā)現(xiàn),錯(cuò)誤不會(huì)被domain捕捉到,原因很清晰了:timer和e兩個(gè)關(guān)鍵的對(duì)象在初始化的時(shí)候都時(shí)沒有在domain的范圍之內(nèi),因此,當(dāng)在next函數(shù)中監(jiān)聽的事件被觸發(fā),執(zhí)行拋出異常的回調(diào)函數(shù)時(shí),其實(shí)根本就沒有處于domain的包裹中,當(dāng)然就不會(huì)被domain捕獲到異常了!
其實(shí)node針對(duì)這種情況,專門設(shè)計(jì)了一個(gè)API:domain.add。它可以將domain之外的timer和event對(duì)象,添加到當(dāng)前domain中去。對(duì)于上面那個(gè)例子:
d.add(timer); //or d.add(e);
將timer或者e任意一個(gè)對(duì)象添加到domain上,就可以讓錯(cuò)誤被domain捕獲了。
再來看最開始redis導(dǎo)致domain無法捕捉到異常的問題。我們是不是也有辦法可以解決呢?
其實(shí)對(duì)于這種情況,還是沒有辦法實(shí)現(xiàn)最佳的解決方案的?,F(xiàn)在對(duì)于非預(yù)期的異常產(chǎn)生的時(shí)候,我們只能夠讓當(dāng)前請(qǐng)求超時(shí),然后讓這個(gè)進(jìn)程停止服務(wù),之后重新啟動(dòng)。graceful模塊配合cluster就可以實(shí)現(xiàn)這個(gè)解決方案。
__domain十分強(qiáng)大,但不是萬能的。__希望在看過這篇文章之后,大家能夠正確的使用domian,避免踩坑。
關(guān)于Node.js 中domain模塊如何使用就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)建站aaarwkj.com,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。
新聞標(biāo)題:Node.js中domain模塊如何使用-創(chuàng)新互聯(lián)
當(dāng)前地址:http://aaarwkj.com/article14/dshjge.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計(jì)公司、網(wǎng)站改版、App開發(fā)、虛擬主機(jī)、網(wǎng)站維護(hù)、全網(wǎng)營銷推廣
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容