最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

異步異常處理

異步異常的特點(diǎn)

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

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無(wú)法捕獲異步中的異常。所以我們能做的只能是:

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所無(wú)法做到的:捕捉異步回調(diào)中出現(xiàn)的異常。

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

var domain = require('domain');

//引入一個(gè)domain的中間件,將每一個(gè)請(qǐng)求都包裹在一個(gè)獨(dú)立的domain中
//domain來(lái)處理異常
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ù)
});

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

詭異的失效

我們的測(cè)試一切正常,當(dāng)正式在生產(chǎn)環(huán)境中使用的時(shí)候,發(fā)現(xiàn)domain突然失效了!它竟然沒有捕獲到異步中的異常,最終導(dǎo)致進(jìn)程異常退出。經(jīng)過(guò)一番排查,最后發(fā)現(xiàn)是由于引入了redis來(lái)存放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)過(guò)一番嘗試,終于將問(wèn)題定位到了:

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剖析

回過(guò)頭來(lái),我們來(lái)看看domain做了些什么來(lái)讓我們捕獲異步的請(qǐng)求(代碼來(lái)自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');

上面這段代碼寫過(guò)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è)賮?lái)看看domain做了些什么。

domain自身其實(shí)是一個(gè)EventEmitter對(duì)象,它通過(guò)事件的方式來(lái)傳遞捕獲的錯(cuò)誤。這樣我們?cè)谘芯克臅r(shí)候,就簡(jiǎn)化到兩個(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)中傳遞:

  1. 當(dāng)domain被實(shí)例化之后,我們通常會(huì)調(diào)用它的run方法(如之前在web服務(wù)中的使用),來(lái)將某個(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事件。
  2. 在require引入domain模塊之后,會(huì)重寫全局的nextTick和_tickCallback,注入一些domain相關(guān)的代碼:
//簡(jiǎ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)通過(guò)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通過(guò)在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,有興趣的童鞋可以在這里看到。

回頭解決疑惑

回過(guò)頭來(lái),我們?cè)賮?lái)看剛才遇到的問(wèn)題:為什么兩個(gè)看上去都是同樣的異步調(diào)用,卻有一個(gè)domain無(wú)法捕獲到異常?理解了原理之后不難想到,肯定是調(diào)用了redis的那個(gè)異步調(diào)用在拋出錯(cuò)誤的這個(gè)事件循環(huán)內(nèi),是不在domain的范圍之內(nèi)的。我們通過(guò)一段更加簡(jiǎn)短的代碼來(lái)看看,到底在哪里出的問(wèn)題。

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捕獲了。

再來(lái)看最開始redis導(dǎo)致domain無(wú)法捕捉到異常的問(wèn)題。我們是不是也有辦法可以解決呢?

其實(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)大,但不是萬(wàn)能的。__希望在看過(guò)這篇文章之后,大家能夠正確的使用domian,避免踩坑。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Node.js?網(wǎng)絡(luò)框架koa?compose中間件使用解析

    Node.js?網(wǎng)絡(luò)框架koa?compose中間件使用解析

    這篇文章主要為大家介紹了Node.js?網(wǎng)絡(luò)框架koa?compose中間件使用解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • node使用UEditor富文本編輯器的方法實(shí)例

    node使用UEditor富文本編輯器的方法實(shí)例

    本篇文章主要介紹了node使用UEditor富文本編輯器的方法實(shí)例,具有一定的參考價(jià)值,有興趣的可以了解一下
    2017-07-07
  • node.js cookie-parser 中間件介紹

    node.js cookie-parser 中間件介紹

    這篇文章主要介紹node.js cookie-parser 中間件,講解的比較詳細(xì),需要的朋友可以參考下。
    2016-06-06
  • node.js的exports使用誤區(qū)示例解釋

    node.js的exports使用誤區(qū)示例解釋

    文章主要介紹了在Node.js中,`exports`和`module.exports`指向同一個(gè)對(duì)象,最終共享的結(jié)果以`module.exports`為準(zhǔn),同時(shí),文章也指出了在使用`require()`導(dǎo)入模塊時(shí),使用的永遠(yuǎn)是`module.exports`指向的對(duì)象實(shí)例
    2024-11-11
  • 利用node.js實(shí)現(xiàn)反向代理的方法詳解

    利用node.js實(shí)現(xiàn)反向代理的方法詳解

    在實(shí)際工程開發(fā)中,會(huì)有前后端分離的需求。為了平滑的完成前端請(qǐng)求到后端各個(gè)獨(dú)立服務(wù),需要一個(gè)中間件實(shí)現(xiàn)請(qǐng)求轉(zhuǎn)發(fā)的功能,利用Nginx可以實(shí)現(xiàn),在這里使用nodejs實(shí)現(xiàn)一個(gè)反向代理服務(wù)器。本文主要介紹了關(guān)于利用node.js實(shí)現(xiàn)反向代理的相關(guān)資料,需要的朋友可以參考下。
    2017-07-07
  • Nodejs關(guān)于gzip/deflate壓縮詳解

    Nodejs關(guān)于gzip/deflate壓縮詳解

    本文主要向大家介紹了nodejs中關(guān)于gzip/deflate壓縮的2種方法,分別是管道壓縮和非管道壓縮,十分詳細(xì),并附帶示例,這里推薦給大家參考下。
    2015-03-03
  • 原生node.js案例--前后臺(tái)交互

    原生node.js案例--前后臺(tái)交互

    本文主要介紹了原生node.js案例--前后臺(tái)交互。本案例包含4部分:(1)HTML部分;(2)ajax部分;(3)JavaScript部分;(4)node服務(wù)器部分。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧
    2017-02-02
  • nodejs 圖片預(yù)覽和上傳的示例代碼

    nodejs 圖片預(yù)覽和上傳的示例代碼

    本篇文章主要介紹了nodejs 圖片預(yù)覽和上傳的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-09-09
  • 基于uniapp與node.js實(shí)現(xiàn)的微信授權(quán)登錄功能實(shí)例

    基于uniapp與node.js實(shí)現(xiàn)的微信授權(quán)登錄功能實(shí)例

    前端一直是一塊充滿驚喜的土地,不僅是那些富有創(chuàng)造性的頁(yè)面,還有那些驚贊的效果及不斷推出的新技術(shù),下面這篇文章主要給大家介紹了關(guān)于如何基于uniapp與node.js實(shí)現(xiàn)的微信授權(quán)登錄功能的相關(guān)資料,需要的朋友可以參考下
    2023-05-05
  • 詳解nodejs如何實(shí)現(xiàn)查詢緩存

    詳解nodejs如何實(shí)現(xiàn)查詢緩存

    對(duì)于頻繁查詢、數(shù)據(jù)穩(wěn)定性高、讀取代價(jià)高的場(chǎng)景,查詢緩存可以發(fā)揮重要的作用,提高系統(tǒng)的性能和用戶體驗(yàn),下面我們就來(lái)學(xué)習(xí)一下nodejs是如何實(shí)現(xiàn)查詢緩存的
    2023-12-12

最新評(píng)論

女性| 南通市| 巫山县| 沈阳市| 博客| 江源县| 陇川县| 从江县| 安泽县| 聂拉木县| 望谟县| 台中县| 新营市| 北碚区| 杂多县| 乐亭县| 监利县| 夏津县| 车致| 奉新县| 黄平县| 内丘县| 尚义县| 涞源县| 抚松县| 丽水市| 阜阳市| 武宁县| 汝州市| 松原市| 固始县| 彩票| 大埔区| 晋城| 米易县| 台前县| 甘肃省| 张家界市| 禹州市| 镇安县| 汽车|