欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Node.js 異步異常的處理與domain模塊解析

 更新時間:2017年05月10日 16:04:01   作者:dead-horse  
本篇文章主要介紹了Node.js 異步異常的處理與domain模塊解析,具有一定的參考價值,感興趣的小伙伴們可以參考一下

異步異常處理

異步異常的特點

由于node的回調(diào)異步特性,無法通過try catch來捕捉所有的異常:

try {
 process.nextTick(function () {
  foo.bar();
 });
} catch (err) {
 //can not catch it
}

而對于web服務而言,其實是非常希望這樣的:

//express風格的路由
app.get('/index', function (req, res) {
 try {
  //業(yè)務邏輯
 } catch (err) {
  logger.error(err);
  res.statusCode = 500;
  return res.json({success: false, message: '服務器異常'});
 }
});

如果try catch能夠捕獲所有的異常,這樣我們可以在代碼出現(xiàn)一些非預期的錯誤時,能夠記錄下錯誤的同時,友好的給調(diào)用者返回一個500錯誤??上В瑃ry catch無法捕獲異步中的異常。所以我們能做的只能是:

app.get('/index', function (req, res) {
 // 業(yè)務邏輯 
});

process.on('uncaughtException', function (err) {
 logger.error(err);
});

這個時候,雖然我們可以記錄下這個錯誤的日志,且進程也不會異常退出,但是我們是沒有辦法對發(fā)現(xiàn)錯誤的請求友好返回的,只能夠讓它超時返回。

domain

在node v0.8+版本的時候,發(fā)布了一個模塊domain。這個模塊做的就是try catch所無法做到的:捕捉異步回調(diào)中出現(xiàn)的異常。

于是乎,我們上面那個無奈的例子好像有了解決的方案:

var domain = require('domain');

//引入一個domain的中間件,將每一個請求都包裹在一個獨立的domain中
//domain來處理異常
app.use(function (req,res, next) {
 var d = domain.create();
 //監(jiān)聽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) {
 //處理業(yè)務
});

我們通過中間件的形式,引入domain來處理異步中的異常。當然,domain雖然捕捉到了異常,但是還是由于異常而導致的堆棧丟失會導致內(nèi)存泄漏,所以出現(xiàn)這種情況的時候還是需要重啟這個進程的,有興趣的同學可以去看看domain-middleware這個domain中間件。

詭異的失效

我們的測試一切正常,當正式在生產(chǎn)環(huán)境中使用的時候,發(fā)現(xiàn)domain突然失效了!它竟然沒有捕獲到異步中的異常,最終導致進程異常退出。經(jīng)過一番排查,最后發(fā)現(xiàn)是由于引入了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
}));

此時,當我們的業(yè)務邏輯代碼中出現(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做了些什么來讓我們捕獲異步的請求(代碼來自node v0.10.4,此部分可能正在快速變更優(yōu)化)。

node事件循環(huán)機制

在看Domain的原理之前,我們先要了解一下nextTick和_tickCallback的兩個方法。

function laterCall() {
 console.log('print me later');
}

process.nextTick(laterCallback);
console.log('print me first');

上面這段代碼寫過node的人都很熟悉,nextTick的作用就是把laterCallback放到下一個事件循環(huán)去執(zhí)行。而_tickCallback方法則是一個非公開的方法,這個方法是在當前時間循環(huán)結束之后,調(diào)用之以繼續(xù)進行下一個事件循環(huán)的入口函數(shù)。

換而言之,node為事件循環(huán)維持了一個隊列,nextTick入隊,_tickCallback出列。

domain的實現(xiàn)

在了解了node的事件循環(huán)機制之后,我們再來看看domain做了些什么。

domain自身其實是一個EventEmitter對象,它通過事件的方式來傳遞捕獲的錯誤。這樣我們在研究它的時候,就簡化到兩個點:

什么時候觸發(fā)domain的error事件:

進程拋出了異常,沒有被任何的try catch捕獲到,這時候將會觸發(fā)整個process的processFatal,此時如果在domain包裹之中,將會在domain上觸發(fā)error事件,反之,將會在process上觸發(fā)uncaughtException事件。

domain如何在多個不同的事件循環(huán)中傳遞:

  1. 當domain被實例化之后,我們通常會調(diào)用它的run方法(如之前在web服務中的使用),來將某個函數(shù)在這個domain示例的包裹中執(zhí)行。被包裹的函數(shù)在執(zhí)行的時候,process.domain這個全局變量將會被指向這個domain實例。當這個事件循環(huán)中,拋出異常調(diào)用processFatal的時候,發(fā)現(xiàn)process.domain存在,就會在domain上觸發(fā)error事件。
  2. 在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();    
 }
};

這個是其在多個事件循環(huán)中傳遞domain的關鍵:nextTick入隊的時候,記錄下當前的domain,當這個被加入隊列中的事件循環(huán)被_tickCallback啟動執(zhí)行的時候,將新的事件循環(huán)的process.domain置為之前記錄的domain。這樣,在被domain所包裹的代碼中,不管如何調(diào)用process.nextTick, domain將會一直被傳遞下去。

當然,node的異步還有兩種情況,一種是event形式。因此在EventEmitter的構造函數(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;
  }
 }

實例化EventEmitter的時候,將會把這個對象和當前的domain綁定,當通過emit觸發(fā)這個對象上的事件時,像_tickCallback執(zhí)行的時候一樣,回調(diào)函數(shù)將會重新被當前的domain包裹住。

而另一種情況,是setTimeout和setInterval,同樣的,在timer的源碼中,我們也可以發(fā)現(xiàn)這樣的一句代碼:

 if (process.domain) timer.domain = process.domain;

跟EventEmmiter一樣,之后這些timer的回調(diào)函數(shù)也將被當前的domain包裹住了。

node通過在nextTick, timer, event三個關鍵的地方插入domain的代碼,讓它們得以在不同的事件循環(huán)中傳遞。

更復雜的domain

有些情況下,我們可能會遇到需要更加復雜的domain使用。

domain嵌套:我們可能會外層有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);
});

為了實現(xiàn)這個功能,其實domain還會偷偷的自己維持一個domain的stack,有興趣的童鞋可以在這里看到。

回頭解決疑惑

回過頭來,我們再來看剛才遇到的問題:為什么兩個看上去都是同樣的異步調(diào)用,卻有一個domain無法捕獲到異常?理解了原理之后不難想到,肯定是調(diào)用了redis的那個異步調(diào)用在拋出錯誤的這個事件循環(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);

此時我們同樣發(fā)現(xiàn),錯誤不會被domain捕捉到,原因很清晰了:timer和e兩個關鍵的對象在初始化的時候都時沒有在domain的范圍之內(nèi),因此,當在next函數(shù)中監(jiān)聽的事件被觸發(fā),執(zhí)行拋出異常的回調(diào)函數(shù)時,其實根本就沒有處于domain的包裹中,當然就不會被domain捕獲到異常了!

其實node針對這種情況,專門設計了一個API:domain.add。它可以將domain之外的timer和event對象,添加到當前domain中去。對于上面那個例子:

d.add(timer);
//or
d.add(e);

將timer或者e任意一個對象添加到domain上,就可以讓錯誤被domain捕獲了。

再來看最開始redis導致domain無法捕捉到異常的問題。我們是不是也有辦法可以解決呢?

其實對于這種情況,還是沒有辦法實現(xiàn)最佳的解決方案的?,F(xiàn)在對于非預期的異常產(chǎn)生的時候,我們只能夠讓當前請求超時,然后讓這個進程停止服務,之后重新啟動。graceful模塊配合cluster就可以實現(xiàn)這個解決方案。

__domain十分強大,但不是萬能的。__希望在看過這篇文章之后,大家能夠正確的使用domian,避免踩坑。

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • Node.JS事件的綁定與觸發(fā)示例詳解

    Node.JS事件的綁定與觸發(fā)示例詳解

    Node中的事件模型就是我們常見的訂閱發(fā)布模式,Nodejs核心API都采用異步事件驅動,所有可能觸發(fā)事件的對象都是一個繼承自EventEmitter類的子類實例對象,這篇文章主要給大家介紹了關于Node.JS事件的綁定與觸發(fā)事件的相關資料,需要的朋友可以參考下
    2022-11-11
  • ubuntu下安裝nodejs以及升級的辦法

    ubuntu下安裝nodejs以及升級的辦法

    本文介紹了ubuntu 12.04服務器安裝nodejs以及升級的方法,ubuntu安裝nodejs以及升級的實例教程,需要的朋友參考下。
    2015-05-05
  • NodeJS實現(xiàn)跨域的方法(使用示例)

    NodeJS實現(xiàn)跨域的方法(使用示例)

    CORS是一種 W3C 標準,它使用額外的 HTTP 頭來告訴瀏覽器讓運行在一個 origin (domain) 上的Web應用被準許訪問來自不同源服務器上的指定的資源,這篇文章主要介紹了NodeJS實現(xiàn)跨域的方法,需要的朋友可以參考下
    2024-05-05
  • 從零開始學習Node.js系列教程四:多頁面實現(xiàn)的數(shù)學運算示例

    從零開始學習Node.js系列教程四:多頁面實現(xiàn)的數(shù)學運算示例

    這篇文章主要介紹了Node.js多頁面實現(xiàn)的數(shù)學運算,涉及nodejs請求響應、數(shù)值傳遞、運算等相關操作技巧,需要的朋友可以參考下
    2017-04-04
  • node.js中的fs.linkSync方法使用說明

    node.js中的fs.linkSync方法使用說明

    這篇文章主要介紹了node.js中的fs.linkSync方法使用說明,本文介紹了fs.linkSync的方法說明、語法、接收參數(shù)、使用實例和實現(xiàn)源碼,需要的朋友可以參考下
    2014-12-12
  • node.js中express-session配置項詳解

    node.js中express-session配置項詳解

    本篇文章主要介紹了node.js中express-session配置項詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • 在?node?中使用?koa-multer?庫上傳文件的方式詳解

    在?node?中使用?koa-multer?庫上傳文件的方式詳解

    本文主要介紹了上傳單個文件、多個文件,文件數(shù)量大小限制、限制文件上傳類型和對上傳的圖片進行不同大小的裁剪,對node使用?koa-multer?庫上傳文件相關知識感興趣的朋友一起看看吧
    2024-01-01
  • Node.js創(chuàng)建子進程的幾種實現(xiàn)方式

    Node.js創(chuàng)建子進程的幾種實現(xiàn)方式

    這篇文章主要介紹了Node.js創(chuàng)建子進程的幾種實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • 詳解如何利用Nodejs構建多進程應用

    詳解如何利用Nodejs構建多進程應用

    這篇文章主要為大家介紹了如何利用Nodejs構建多進程應用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-10-10
  • 2014年最火的Node.JS后端框架推薦

    2014年最火的Node.JS后端框架推薦

    用nodejs開發(fā)web應用,用哪個框架好?express?還是其他什么?今天小編就來給大家推薦一下今年最好用的幾款Node.js后端框架
    2014-10-10

最新評論