1. 程式人生 > >解讀node.js的cluster模組

解讀node.js的cluster模組

       在如今機器的CPU都是多核的背景下,Node的單執行緒設計已經沒法更充分的"壓榨"機器效能了。所以從v0.8開始,Node新增了一個內建模組——“cluster”,故名思議,它可以通過一個父程序管理一坨子程序的方式來實現叢集的功能

快速上手

使用十分的簡單,如下

var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length; // 獲取CPU的個數
 
if (cluster.isMaster) {
    for (var i = 0; i < numCPUs; i++) {
        cluster.fork();
    }
 
    cluster.on('exit', function(worker, code, signal) {
        console.log('worker ' + worker.process.pid + ' died');
    });
} else {
    http.createServer(function(req, res) {
        res.writeHead(200);
        res.end("hello world\n");
    }).listen(8000);
}

        稍微解釋下,通過isMaster屬性,判斷是否Master程序,是則fork子程序,否則啟動一個server。每個HTTP server都能監聽到同一個埠。

        但是在實際專案中,我們的啟動程式碼一般都已經封裝在了app.js中,要把整塊啟動邏輯嵌在上面的if else中實在不優雅。 所以,我們可以這樣:

var cluster = require('cluster');
var numCPUs = require('os').cpus().length;
 
if (cluster.isMaster) {
    for (var i = 0; i < numCPUs; i++) {
        cluster.fork();
    }
    // 其它程式碼
    
} else {
    require("./app.js");
}

        簡單之處就在於原本的應用邏輯根本不需要知道自己是在叢集還是單邊。(當然,如果應用在記憶體中維護了某些狀態,比如session,就需要運用某些機制來共享了,這裡不詳說)

常用API

        cluster模組提供了一大坨事件和方法,這裡挑一些常用的說明下,詳細的請參考官方文件。

cluster.setupMaster([settings])

       setupMaster用來改變預設設定,只能被呼叫一次,呼叫後,配置會存在且凍結在cluster.settings裡。配置只會影響fork時的行為,實際上這些選項就是傳給fork用的,有興趣的同學可以去對照child_process.fork()的引數。

       具體有如下選項:

  • execArgv Node執行時的變數陣列,傳遞給node(預設為process.execArgv)。
  • exec 執行的檔案,配置後就不需要像最開始的例子,在程式碼裡require目標檔案了(預設為process.argv[1])。
  • args 傳遞給worker的變數陣列(預設為process.argv.slice(2)))。
  • silent 是否禁止列印內容(預設為false)。
  • uid 設定程序的使用者ID。
  • gid 設定程序的組ID。

Event: fork和online

       當一個新的worker被fork時就會觸發fork事件,而在worker啟動時才會觸發online事件,所以fork先觸發,online後觸發。可以在這兩個事件的callback裡做些初始化的邏輯,也可以在這時向master報告:“我起來了!”。

Event: exit

       當任何一個worker停掉都會觸發exit事件,可以在回撥裡增加fork動作重啟。

       通過worker.suicide來判斷,worker是意外中斷還是主動停止的(在worker中呼叫kill和disconnect方法,視作suide。)。

cluster.on('exit', function(worker, code, signal) {
    console.log('worker %d died (%s). restarting...',
        worker.process.pid, signal || code);
    cluster.fork();
});

cluster.worker和cluster.workers

       前者是一份worker物件的引用,只能在worker裡使用。後者是master下對當前可用worker的一個Object,key為worker id,注意,當worker已經exit或disconnect後就不會在這個object裡了。

Event: message

       message事件可以用來做master和worker的通訊機制。

       利用這套機制,可以用來實現不間斷重啟

       文章最開始的例子有個問題,尤其是執行在生產環境還不夠健壯:如果某個worker因為意外“宕機”了,程式碼並沒有任何處理,這時如果我們重啟應用又會造成服務中斷。利用這些API就可以利用事件監聽的方式做相應處理。

原理

       每個worker程序通過使用child_process.fork()函式,基於IPC(Inter-Process Communication,程序間通訊),實現與master程序間通訊。

       什麼是fork,Linux API給瞭如下解釋

fork() creates a new process by duplicating the calling process. The new process is referred to as the child process. The calling process is referred to as the parent process.

The child process and the parent process run in separate memory spaces. At the time of fork() both memory spaces have the same content. Memory writes, file mappings (mmap(2)), and unmappings (munmap(2)) performed by one of the processes do not affect the other.

       我們可以看到,fork出的子程序擁有和父程序一致的資料空間、堆、棧等資源(fork當時),但是是獨立的,也就是說二者不能共享這些儲存空間。 那我們直接用fork自己實現不就行了,幹嘛需要cluster呢。

        “這樣的方式僅僅實現了多程序。多程序執行還涉及父子程序通訊,子程序管理,以及負載均衡等問題,這些特性cluster幫你實現了。”

       這裡再說下cluster的負載均衡。Node.js v0.11.2+的cluster模組使用了round-robin排程演算法做負載均衡,新連線由主程序接受,然後由它選擇一個可用的worker把連線交出去,說白了就是輪轉法。演算法很簡單,但據官方說法,實測很高效。

       注意:在windows平臺,預設使用的是IOCP,官方文件說一旦解決了分發handle物件的效能問題,就會改為RR演算法(沒有時間表。。)

       如果想用作業系統指定的演算法,可以在fork新worker之前或者setupMaster()之前指定如下程式碼:

cluster.schedulingPolicy = cluster.SCHED_NONE;

       或者通過環境變數的方式改變

$ export NODE_CLUSTER_SCHED_POLICY="none" # "rr" is round-robin
$ node app.js

       或在啟動Node時指定

$ env NODE_CLUSTER_SCHED_POLICY="none" node app.js

使用pm2實現cluster

       pm2是一個現網程序管理的工具,可以做到不間斷重啟、負載均衡、叢集管理等,比forever更強大。利用pm2可以做到no code but just config實現應用的cluster。

      安裝pm2什麼的這裡就不贅述了。用pm2啟動時,通過-i指定worker的數量即可。如果worker掛了,pm2會自動立刻重啟,各種簡單省心。

$ pm2 start app.js -i 4

      也可以在應用執行時,改變worker的數量,如下圖


     更多的使用方法,可以去github上慢慢看(說句題外話,如果有類似PM2,甚至更好的PM工具,歡迎在評論裡回覆^_^)。

多機器叢集

      cluster適用於在單臺機器上,如果應用的流量巨大,多機器是必然的。這時,反向代理就派上用場了,我們可以用node來寫反向代理的服務(比如用http-proxy),好處是可以保持工程師技術棧的統一,不過生產環境,我們用的更多的還是nginx,這裡就不多介紹了。