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


1 cluster多進程

cluster經過好幾代的發展,現在已經比較好使了。利用cluster,可以自動完成子進程worker分配request的事情,就不再需要自己寫代碼在master進程中robin式給每個worker分配任務了。

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呢,還是一個核對應2、3個worker呢,就視情況而定了。如果項目中,等待其他服務器(例如數據庫)響應特別長時間,設置2個以上worker應該會更好。

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

 

那么,整個架構就類似這樣:

image

 

Master進程,需要做的就是監控worker的生命周期,如果發現worker掛掉了,就重啟worker,並做好相應的log。

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

 

多進程的架構,相對原始的單進程+pm2重啟好處肯定多很多,整個node服務會更穩定,不會突然徹底掛了。

另外,對比pm2多進程,也有優勢,主要是master的邏輯掌握在開發自己手中,可以做好自定義的log和郵件、短信告警。

 

為了整個nodejs服務管理方便,在master進程中,我們一般開啟管理端口的監聽,例如12701,通過命令行curl 127.0.0.1:12701:xxx發起一個簡單的http get請求,輕松管理。

例如xxx傳入reload,可以作為服務器重啟的指令。

 

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

對於小企業還好,可以安排在凌晨重啟,但對於大公司大產品來說,就不能這么粗暴了。

 

那么我們需要平滑重啟,實現重啟過程中,服務不中斷。

策略並不復雜,但非常有效:

1、worker進程輪流重啟,間隔時間;

2、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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM