node.js cluster多進程、負載均衡和平滑重啓

1 cluster多進程

cluster通過好幾代的發展,如今已經比較好使了。利用cluster,能夠自動完成子進程worker分配request的事情,就再也不須要本身寫代碼在master進程中robin式給每一個worker分配任務了。node

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  // Fork workers.
  for (var i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`worker ${worker.process.pid} died`);
  });
} else {
  // Workers can share any TCP connection
  // In this case it is an HTTP server
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('hello world\n');
  }).listen(80);
}

 

上述簡單的代碼,就實現了根據CPU個數,建立多個worker。至於實際項目中,是正好一個核對應一個worker呢,仍是一個核對應二、3個worker呢,就視狀況而定了。若是項目中,等待其餘服務器(例如數據庫)響應特別長時間,設置2個以上worker應該會更好。linux

不過通常而言,一個CPU對一個worker就挺好的了。shell

 

那麼,整個架構就相似這樣:數據庫

image

 

Master進程,須要作的就是監控worker的生命週期,若是發現worker掛掉了,就重啓worker,並作好相應的log。服務器

整個架構沒有太大的難點,重點就是作好一些細節處理,例如重啓、日誌、5秒心跳包等。架構

 

多進程的架構,相對原始的單進程+pm2重啓好處確定多不少,整個node服務會更穩定,不會忽然完全掛了。負載均衡

另外,對比pm2多進程,也有優點,主要是master的邏輯掌握在開發本身手中,能夠作好自定義的log和郵件、短信告警。curl

 

爲了整個nodejs服務管理方便,在master進程中,咱們通常開啓管理端口的監聽,例如12701,經過命令行curl 127.0.0.1:12701:xxx發起一個簡單的http get請求,輕鬆管理。ui

例如xxx傳入reload,能夠做爲服務器重啓的指令。this

 

2 負載均衡

說到多進程,目的確定是儘量利用多核CPU,提升單機的負載能力。

但每每在實際項目中,受到業務邏輯的處理時間長短和系統CPU調度影響,致使實際上全部進程的負載並非理想的完全均衡。

官方也說了:

In practice however, distribution tends to be very unbalanced due to operating system scheduler vagaries. Loads have been observed where over 70% of all connections ended up in just two processes, out of a total of eight.

翻譯一下:70%的請求最終都落到2個worker身上,而這2個worker佔用更多的CPU資源。

那麼在實際項目部署,咱們能夠嘗試更進一步的措施:綁定CPU。4核CPU,咱們fork出4個worker,每一個worker分別綁定到#1-#4 CPU。

node並無給咱們提供現成的接口,不過咱們可使用linux的命令:taskset

在node中,咱們可使用child_process執行shell。

cp.exec('taskset -cp ' + (cpu) + ' ' + process.pid,{ 
    timeout: 5000 
},function(err,data,errData){ 
    if(err){ 
        logger.error(err.stack); 
    } 
    
    if(data.length){ 
        logger.info('\n' + data.toString('UTF-8')); 
    } 
    
    if(errData.length){ 
        logger.error('\n' + errData.toString('UTF-8')); 
    } 
});

 

按實際狀況來看,效果是不錯的。

imageimageimageimage

 

 

3 平滑重啓

每次發佈新版本,服務器必然須要重啓。

簡單粗暴的,殺掉主進程,所有重啓,必然會有一段時間的服務中斷。

image

對於小企業還好,能夠安排在凌晨重啓,但對於大公司大產品來講,就不能這麼粗暴了。

 

那麼咱們須要平滑重啓,實現重啓過程當中,服務不中斷。

策略並不複雜,但很是有效:

一、worker進程輪流重啓,間隔時間;

二、worker進程並非直接重啓,而是先關閉新請求監聽,等當前請求都返回了,再重啓。

  try {
        // make sure we close down within 30 seconds
        var killtimer = setTimeout(() => {
          process.exit(1);
        }, 30000);

        // stop taking new requests.
        server.close();

        // Let the master know we're dead.  This will trigger a
        // 'disconnect' in the cluster master, and then it will fork
        // a new worker.
        cluster.worker.disconnect();

      } catch (er2) {
      }

 

實施了平滑重啓後,服務器的吞吐率會平滑不少。

image

相關文章
相關標籤/搜索