Cluster(集群)[翻譯]

Node.js v10.0.0 Documentation?

nodejs.org

一個Node.js實例運行在一個線程里。為了利用系統多核的優勢,用戶有些時候想通過啟動一群Node.js進程去處理負載。

cluster模塊可以輕鬆的創建共享相同埠的子進程。

const cluster = require(cluster);const http = require(http);const numCPUs = require(os).cpus().length;if (cluster.isMaster) {console.log(`Master ${process.pid} is running`);// Fork workers.for (let 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 serverhttp.createServer((req, res) => {res.writeHead(200);res.end(hello world
);}).listen(8000);console.log(`Worker ${process.pid} started`);}

現在運行Node.js工作進程之間將分享8000埠:

$ node server.jsMaster 3596 is runningWorker 4324 startedWorker 4520 startedWorker 6056 startedWorker 5644 started

請注意,Windows系統中的工作進程上現在還不能設置一個命名管道伺服器。

它是如何工作的

child_process.fork()方法產生工作進程,所以它們可以跟父進程通過IPC通信,並且來回傳遞服務句柄。

cluster模塊支持兩種分配傳入連接。

第一種方式(除了Windows外默認的方式)是循環法,主進程監聽一個埠接收新的連接,通過循環的方式分配給子進程,並且通過其中的一些內置智能避免子進程過載。

第二種方式是主進程創建監聽的socket,並且把它傳遞給感興趣的工作進程。然後,工作進程直接接收過來的連接。

理論上,第二種方式能提供最好的性能。但是在實踐中,由於操作系統調度程序變化莫測,導致分配十分不均衡。在8個進程中其中的兩個進程載入了70%的連接。

因為server.listen()把絕大多數工作都交給了主進程,因此在一個普通的Node.js進程和一個集群工作進程(a cluster worker)之間有三個不同點:

1、server.listen({fd: 7}) 因為消息被傳遞給主進程,在父進程中文件描述符7將會被監聽,並且將該句柄傳遞到工作進程,而不是監聽工作進程對文件描述符7引用的意圖。

2、server.listen(handle) 明確的監聽句柄使工作進程使用提供的句柄,而不是跟主進程對話。

3、server.listen(0) 通常情況下,這會使伺服器監聽任意一個介面。然而,在一個集群中使用listen(0)時,每一個工作進程都會接收到相同的「隨機」埠。本質上,埠第一時間是隨機的,但是以後是可以預見的。如果要監聽一個唯一埠,可以根據集群工作進程ID來生成一個埠號。

Node.js沒有提供路由邏輯。因此設計一個不過渡依賴像session和登錄這樣的內存數據對象的應用非常重要。

因為每個工作進程都是獨立的,它們可以在不影響其它工作進程的情況下,根據程序需要被殺死或者重新生成。只要有工作進程存活,伺服器就會繼續接收連接。如果沒有存活的工作進程,已有的連接將會被丟棄,新的連接將會被拒絕。然而,Node.js並不會自動管理工作進程的數量。應用程序根據自己的需求管理工作進程池。

雖然cluster模塊的主要應用場景是網路,但它同樣可以應用在其它需要工作進程的地方。

Class: Worker

在v0.7.0添加

一個Worker對象包含了所有worker的公共信息和方法。在主進程中可以使用cluster.workers。在工作進程中可以使用cluster.worker。

Event:disconnect

在v0.7.7添加

跟cluster.on(『disconnect』)事件相同,但是它是特定屬於該工作者。

cluster.fork().on(disconnect, () => {// Worker has disconnected});

Event:error

在v0.7.3添加

該事件跟child_process.fork()提供的』error』事件相同。

在一個工作進程中,process.on(『error』)可能被使用。

Event:exit

在v0.11.2

  • code <number> 如果正常退出的退出碼。
  • signal <string> 導致進程被殺死的信號的名稱(例如:』SIGHUP』)。

跟cluster.on(『exit』)相同,但是只針對這一個工作進程。

const worker = cluster.fork();worker.on(exit, (code, signal) => {if (signal) {console.log(`worker was killed by signal: ${signal}`);} else if (code !== 0) {console.log(`worker exited with error code: ${code}`);} else {console.log(worker success!);}});

Event:listening

在v0.7.0添加

  • address <Object>

跟cluster.on(『listening』)事件相同,但是只針對這個工作進程。

cluster.fork().on(listening, (address) => {// Worker is listening});

在工作進程中不會發射。

Event:message

在v0.7.0添加

  • message <Object>
  • handle <undefined> | <Object>

跟cluster的』message』事件相同,但是只針對這個工作進程。

在一個工作進程里,process.on(『message』)也可以被使用。

參考 process event: 『message』。

例子,下面是一個集群,它在主進程中使用消息系統保持對請求數量的統計:

const cluster = require(cluster);const http = require(http);if (cluster.isMaster) {// Keep track of http requestslet numReqs = 0;setInterval(() => {console.log(`numReqs = ${numReqs}`);}, 1000);// Count requestsfunction messageHandler(msg) {if (msg.cmd && msg.cmd === notifyRequest) {numReqs += 1;}}// Start workers and listen for messages containing notifyRequestconst numCPUs = require(os).cpus().length;for (let i = 0; i < numCPUs; i++) {cluster.fork();}for (const id in cluster.workers) {cluster.workers[id].on(message, messageHandler);}} else {// Worker processes have a http server.http.Server((req, res) => {res.writeHead(200);res.end(hello world
);// notify master about the requestprocess.send({ cmd: notifyRequest });}).listen(8000);}

Event:online

在v0.7.0添加

跟cluster.on(『online』)事件一樣,但是只針對這個工作進程。

cluster.fork().on(online, () => {// Worker is online});

在工作進程中不發射。

worker.disconnect()

歷史

v7.3.0 現在這個方法返回一個worker的引用。

v0.7.7 在v0.7.7添加

  • 返回:<cluster.Worker> 一個worker的引用。

在工作進程中,這個函數會關閉所有服務,在所有這些服務中等待』close』事件,然後斷開IPC通道。

導致.exitedAfterDisconnect被設置。

注意,當一個服務關閉後,它將不會接收新的連接,但是連接可以被其它工作進程接收。已存在的連接允許正常關閉。當沒有連接存在時(參考server.close()),工作進程的IPC通道會關閉從而允許工作進程優雅退出。

上述的內容只適用於伺服器連接,客戶端連接不會被工作進程自動關閉,並且在退出之前斷開並不會等它們關閉。

注意在工作進程中,雖然有process.disconnect,但是並不是這個函數,而是disconnect[subprocess.disconnect()]。

因為長時間存活的服務連接可能會阻止工作進程斷開,可能發送一個消息比較有用,以便應用的特定動作可能會被用來關閉它們。殺死工作進程時,如果很長時間後都沒有發射』disconnect』事件,所以實現超時也可能有用。

if (cluster.isMaster) {const worker = cluster.fork();let timeout;worker.on(listening, (address) => {worker.send(shutdown);worker.disconnect();timeout = setTimeout(() => {worker.kill();}, 2000);});worker.on(disconnect, () => {clearTimeout(timeout);});} else if (cluster.isWorker) {const net = require(net);const server = net.createServer((socket) => {// connections never end});server.listen(8000);process.on(message, (msg) => {if (msg === shutdown) {// initiate graceful close of any connections to server}});}

worker.exitedAfterDisconnect

在v6.0.0添加

  • <boolean>

調用.kill()或者.disconnect()時被設置。再次之前都是undefined。

worker.exitedAfterDisconnect這個布爾值可以區分是自願退出還是異常退出,主進程可以根據這個值來重新生成工作進程。

cluster.on(exit, (worker, code, signal) => {if (worker.exitedAfterDisconnect === true) {console.log(Oh, it was just voluntary – no need to worry);}});// kill workerworker.kill();

worker.id

在v0.8.0添加

  • <number>

每一個工作進程都有一個唯一的ID,這個ID被存儲在**id**里。

當進程存活時,它是在cluster.workers中查找工作進程的索引。

worker.isConnected()

在v0.11.14添加

當工作進程是通過IPC通道連接的主進程,這個函數返回true,否則返回false。當工作進程被創建後,它被連接到主進程。當』disconnect』事件被發射後斷開連接。

worker.isDead()

在v0.11.14添加

當工作進程終止(不管是因為退出或者信號傳遞)後該函數返回true。否則,返回false。

worker.kill([signal=SIGTERM』])

在v0.9.12添加

  • signal <string> 發送給工作進程的kill信號量的名字。

這個方法將會殺死工作進程。在主進程中,通過關閉worker.process的連接來實現,一旦關閉連接,然後通過signal殺死。在工作進程中,通過關閉通道連接實現,然後以0退出碼退出。

導致.exitedAfterDisconnect被設置。

此方法通過別名worker.destroy()向後兼容。

注意,在工作進程中存在process.kill(),但是它不是這個功能,它的功能是kill [process.kill(pid[, signal])]。

worker.process

在v0.7.0添加

  • <ChildProcess>

所有的工作進程都是使用child_process.fork()創建,這個函數返回的對象被存儲為.process。在子進程中,全局process已經被存儲。

參考: Child Process module

注意,如果process上發生』disconnect』事件並且.exitedAfterDisconnect不是true,工作進程會調用process.exit(0)。這可以防止意外斷開連接。

worker.send(message[,sendHandle][,callback])

歷史

v0.7.0 在v0.7.0添加。

v4.0.0 這個版本支持callback參數。

  • message <Object>
  • sendHandle <Handle>
  • callback <Function>
  • 返回:<boolean>

發送給工作進程或者主進程消息,可以選擇附帶句柄。

在主進程中發送消息給特定的工作進程。它跟ChildProcess.send()一樣。

工作進程發送消息給主進程。它跟process.send()一樣。

下面的例子會把主進程發送的消息全部回傳回去:

if (cluster.isMaster) {const worker = cluster.fork();worker.send(hi there);} else if (cluster.isWorker) {process.on(message, (msg) => {process.send(msg);});}

Event:disconnect

在v0.7.9添加

  • worker <cluster.Worker>

在工作進程的IPC通道斷開連接後發射。當一個工作進程優雅退出後將會發生,比如被殺死或者手工斷開連接(例如:通過worker.disconnect())。

在』disconnect』和』exit』事件之間可能會有延時。這些事件可以被用作檢查進程是否在清理過程中被卡住,或者它們是否有長連接。

cluster.on(disconnect, (worker) => {console.log(`The worker #${worker.id} has disconnected`);});

Event: exit

在v0.7.9添加

  • worker <cluster.Worker>
  • code <number> 正常退出時的退出碼
  • signal <string> 導致進程被殺死的信號量的名字(例如:』SIGHUP』)

任何工作進程死掉cluster模塊都會發射』exit』事件。

通過再次調用.fork()重新啟用工作進程。

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

參考child_process event: 『exit』。

Event: fork

在v0.7.0添加

  • worker <cluster.Worker>

當工作進程被創建的時候cluster模塊將發射』fork』事件。這樣可以記錄工作進程的活動,並且創建一個自定義超時。

const timeouts = [];function errorMsg() {console.error(Something must be wrong with the connection ...);}cluster.on(fork, (worker) => {timeouts[worker.id] = setTimeout(errorMsg, 2000);});cluster.on(listening, (worker, address) => {clearTimeout(timeouts[worker.id]);});cluster.on(exit, (worker, code, signal) => {clearTimeout(timeouts[worker.id]);errorMsg();});

Event:listening

在v0.7.0添加

  • worker <cluster.Worker>
  • address <Object>

在一個工作進程調用listen()之後,當一個』listening』事件在服務端被發射時,在主進程的cluster中同樣發射』listening』事件。

事件句柄包括兩個參數,worker參數包含工作進程對象,address對象包含連接屬性:address、port和addressType。這對於工作進程監聽多個地址非常有用。

cluster.on(listening, (worker, address) => {console.log(`A worker is now connected to ${address.address}:${address.port}`);});

addressType是以下內容中的一個:

  • 4(TCPv4)
  • 6(TCPv6)
  • -1(unix域套接字)
  • 『udp4』或者』udp6』(UDP v4或者v6)

Event:message

歷史

v6.0.0 worker參數被傳遞;詳情參考下面。

v2.5.0 在v2.5.0添加

  • worker <cluster.Worker>
  • message <Object>
  • handle <undefined> | <Object>

當集群主服務收到任何工作進程的消息的時候發射。

參考child_process event:』message』。

在Node.js v6.0之前,這個事件只發射消息和句柄,並不包括工作進程對象。

如果需要兼容老版本,但是並不需要工作進程,可以通過參數的長度來判斷:

cluster.on(message, (worker, message, handle) => {if (arguments.length === 2) {handle = message;message = worker;worker = undefined;}// ...});

Event:online

在v0.7.0添加

  • worker <cluster.Worker>

在創建一個工作進程之後,工作進程應該回應一個在線消息。當主進程接收到一個在線消息時,它會發射這個事件。』fork』跟』online』的區別在於,』fork』是在主進程生成一個工作進程時發射,而』online』是在工作進程運行時發射。

cluster.on(online, (worker) => {console.log(Yay, the worker responded after it was forked);});

Event:setup

在v0.7.1添加

  • settings <Object>

每次調用.setupMaster()時發射。

settings對象是在調用.setupMaster時設置的cluster.settings對象,並且這個對象只是建議性的,因為在一個周期內.setupMaster()可以被調用多次。

如果精確度非常重要,使用cluster.settings。

cluster.disconnect([callback])

在v0.7.7添加

  • callback <Function> 當所有工作進程斷開連接和句柄被關閉時調用。

在每一個cluster.workers中的工作進程中調用.disconnect()。

當所有工作進程被斷開連接,它們內部的句柄也將會關閉,如果沒有事件在等待,現在允許主進程優雅退出。

這個方法的回調函數參數是可選的,它將會在結束後調用。

這個函數只能在主進程中被調用。

cluster.fork([env])

在v0.6.0添加

  • env <Object> 添加到工作進程環境的鍵值對。
  • 返回:<cluster.Worker>

產生一個新的工作進程。

這個只能在主進程中調用。

cluster.isMaster

在v0.8.1添加

  • <boolean>

返回ture則進程是主進程。這是由process.env.NODE_UNIQUE_ID決定的。如果process.env.NODE_UNIQUE_ID是未定義的,則isMaster返回true。

cluster.isWorker

在v0.6.0添加

  • <boolean>

當進程不是主進程時返回true。

cluster.schedulingPolicy

在v0.11.2添加

調度策略,cluster.SCHED_RR的循環或者cluster.SCHED_NONE的把調度交給操作系統。這是一個全局設置,並且當第一個工作進程產生或者cluster.setupMaster被調用的時候就被凍結,先到先得。

SCHED_ERR在除了Windows意外的平台上都是默認的。當libuv在分配IOCP句柄的時候不會引起大的性能問題,Windows會切換到SCHED_RR。

cluster.schedulingPolicy可以通過NODE_CLUSTER_SCHED_POLICY環境變數來設置。有效的值為』rr』和』none』。

cluster.settings

歷史

v9.5.0 添加cwd選項支持

v9.4.0 添加windowsHide選項支持

v8.2.0 添加inspectPort選項支持

v6.4.0 添加stdio選項支持

v0.7.1 在v0.7.1添加

  • <Object>
    • execArgv <string[]> 傳遞給Node.js可執行的字元串參數列表。默認:process.execArgv。
    • exec <string> 工作文件的目錄。默認: process.argv[1]。
    • args <string[]> 傳遞到子進程的字元串參數。默認:process.argv.slice(2)。
    • cwd <string> 當前工作進程的工作目錄。默認:undefined(從父進程繼承)。
    • silent <boolean> 是否把輸出發送到父進程的標準輸入輸出(stdio)。默認:false。
    • stdio <Array> 配置生成的進程的標準輸入輸出(stdio)。因為集群模塊依賴IPC運行,所以配置必須包含一個』ipc』條目。當提供了這個選項,silent將會被覆蓋。
    • uid <number> 設置進程的用戶唯一標識。(參考setuid(2))
    • gid <number> 設置進程的組唯一標識。(參考setgid(2))
    • inspectPort <number> | <Function> 設置工作進程的檢測埠。這可以是一個數字,或者一個沒有參數的函數,並且返回一個數字。默認每個工作進程獲得它自己的埠,通過增加主進程的process.debugPort產生。
    • windowsHide <boolean> 隱藏通常在Windows系統上創建的進程式控制制台窗口。默認:false。

這個設置對象在調用.setupMaster()(或者.fork())之後將會包含這些設置,包括默認值。

這個對象不會被改變和手動設置。

cluster.setupMaster([settings])

歷史

v6.4.0 添加stdio選項支持

v0.7.1 在v0.7.1添加

  • settings <Object> 參考cluster.settings

setupMaster用來改變默認的』fork』方式。一旦被調用,設置將會提現在cluster.settings。

注意這些:

  • 任何設置改變隻影響將來通過.fork()創建的進程,原來的進程不受影響。
  • env是唯一不能通過.setupMaster()來給.fork()設置的工作進程屬性。
  • 上述的默認值只適用於第一次調用,以後的默認值是cluster.setupMaster()被調用時的值。

例子:

const cluster = require(cluster);cluster.setupMaster({exec: worker.js,args: [--use, https],silent: true});cluster.fork(); // https workercluster.setupMaster({exec: worker.js,args: [--use, http]});cluster.fork(); // http worker

這個只能在主進程中執行。

cluster.worker

在v0.7.0添加

  • <Object>

當前工作進程對象的引用。在主進程中不可用。

const cluster = require(cluster);if (cluster.isMaster) {console.log(I am master);cluster.fork();cluster.fork();} else if (cluster.isWorker) {console.log(`I am worker #${cluster.worker.id}`);}

cluster.workers

在v0.7.0添加

  • <Object>

一個存儲存活的工作對象的哈希表,鍵為id欄位。工作進程可以很容易的被循環。這個方法只在主進程中可見。

在工作進程斷開連接和退出後在cluster.workers中被移除。這兩個事件的順序不能被提前預測。然而,可以保證的是cluster.workers的移除動作發生在最後的『disconnect』或者』exit』事件被發射之前。

// Go through all workersfunction eachWorker(callback) {for (const id in cluster.workers) {callback(cluster.workers[id]);}}eachWorker((worker) => {worker.send(big announcement to all workers);});

使用工作進程的唯一id號是查找進程的最好方式。

socket.on(data, (id) => {const worker = cluster.workers[id];});

推薦閱讀:

如何解決大規模機器學習的三大痛點?
Docker 引擎的 Swarm 模式:入門教程
【技術貼】如何部署Apache Kylin集群實現負載均衡?
[轉]三大主流軟體負載均衡器對比(LVS、Nginx、HAproxy)

TAG:科技 | Nodejs | 集群 |