Node.js實現100%線上的要則

banq發表於2014-01-12


這是針對NodeJS的生產環境中如何保證100%線上不停機的文章。


1.使用Domain模組處理未捕獲的異常

未捕獲的異常發生時,錯誤被丟擲一個try/ catch塊以外,或者一個錯誤事件發出後,並沒有誰去偵聽它。NodeJS對待未捕獲異常的預設操作是退出崩潰的程式,如果這個程式是一個伺服器,就導致伺服器停機,所以我們要儘可能避免未捕獲的異常,並且在它們發生時明智地處理它們。

使用Domain編寫堅固的程式碼和測試來完成這個一目的。NodeJS的Domain模組.

Domain的方法有:
add: 繫結一個發射器到domain.
run: 在domain場景下執行
bind: 繫結一個函式
intercept: 類似繫結但是不處理第一個引數錯誤
dispose: 取消IO 和 timers.

使用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);
});
<p class="indent">


執行一個普通函式的案例:

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);
  });
});
<p class="indent">


domain能夠捕獲非同步錯誤:

var d = require('domain').create();

d.on('error', function (err) {
  console.log("domain caught", err);
});

var f = d.bind(function() {
  console.log(domain.active === d); // <-- true
  throw new Error("uh-oh");
});

setTimeout(f, 100);
<p class="indent">

上述f函式如果不繫結在domain場景下執行,其丟擲的錯誤會使NodeJS崩潰,這裡domain的錯誤處理器被呼叫,給你一個機會採取行動,比如重試忽略等。

當程式碼在Domain中執行時,domain是活動的,你可以透過domain.active 包含引用它,新的事件發射器將繫結到這個活動的domain。

2.使用cluster管理程式
domain並不足以完成持續線上不當機,有時一個獨立的程式因為一些錯誤需要停止,使用NodeJS的Cluster模組,我們能分配一個主程式管理一個或多個工作程式,並且分佈工作給它們,主程式需要不停止的一直執行,意味著socket總是能接受連線,當一個worker工作程式停止工作,主程式能替代它。

當我們初始化一個工作程式的載入好後,我們使用傳送訊號給主程式,可以給主程式分配Unix的符號連結symlink指向工作者程式碼,然後更新符號連結指向到一個新的程式碼版本,傳送訊號給主程式讓它關閉一箇舊的工作程式,開啟新的工作程式,新的工作程式將用新的程式碼版本執行。

主程式和工作程式的協調通訊很重要,工作程式使用它們狀態進行通訊,主程式必須知道基於這些狀態採取何種行動,recluster是對node的原生cluster模組一個的包裝,能提供工作程式的狀態管理。

當工作程式需要關閉時,它們必須優雅地關閉,因為它們服務於很多請求,有大量的tcp連線,為了關閉這些存在的連線,我們必須增加中介軟體來檢查應用是否可以進入一個正在關閉的狀態,如果是就立即關閉。

var afterErrorHook = function(err) {
server.close(); // <-- ensure no new connections
}
呼叫server.close能確保沒有新的連線

下面是關閉一個正在活動的程式:

var afterErrorHook = function(err) { // <-- called after an unrecoverable error
  app.set("isShuttingDown", true); // <-- set state
  server.close(function() {
    process.exit(1);  // <-- all clear to exit
  });
}

var shutdownMiddle = function(req, res, next) {
  if(app.get("isShuttingDown") {  // <-- check state
    req.connection.setTimeout(1);  // <-- kill keep-alive
  }
  next();
}
<p class="indent">

我們可以在一個計時器timer中呼叫process.exit。

相關文章