# 集群
~~~
穩(wěn)定度: 1 - 實(shí)驗(yàn)性
~~~
單個(gè) Node 實(shí)例運(yùn)行在單個(gè)線程中。要發(fā)揮多核系統(tǒng)的能力,用戶有時(shí)候需要啟動(dòng)一個(gè) Node 進(jìn)程集群來(lái)處理負(fù)載。
集群模塊允許你方便地創(chuàng)建一個(gè)共享服務(wù)器端口的進(jìn)程網(wǎng)絡(luò)。
~~~
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
if (cluster.isMaster) {
cluster.fork();
cluster.fork();
console.log('master pid:' + process.pid);
} else {
// Workers can share any TCP connection
// In this case its a HTTP server
http.createServer(function(req, res) {
res.writeHead(200);
res.end("hello world\n");
}).listen(8000);
console.log('child pid:' + process.pid);
}
~~~
現(xiàn)在,運(yùn)行 node 將會(huì)在所有工作進(jìn)程間共享 8000 端口:
~~~
% NODE_DEBUG=cluster node server.js
23521,Master Worker 23524 online
23521,Master Worker 23526 online
23521,Master Worker 23523 online
23521,Master Worker 23528 online
~~~
這是一個(gè)近期推出的功能,在未來(lái)版本中可能會(huì)有所改變。請(qǐng)嘗試并提供反饋。
還要注意的是,在 Windows 中尚不能在工作進(jìn)程中建立一個(gè)被命名的管道服務(wù)器。
### 它是如何工作的
工作進(jìn)程是通過(guò)使用 `child_process.fork` 方法派生的,因此它們可以通過(guò) IPC(進(jìn)程間通訊)與父進(jìn)程通訊并互相傳遞服務(wù)器句柄。
集群模塊支持兩種分配傳入連接的方式。
第一種(同時(shí)也是除 Windows 外所有平臺(tái)的缺省方式)為循環(huán)式:主進(jìn)程監(jiān)聽一個(gè)端口,接受新連接,并以輪流的方式分配給工作進(jìn)程,并以一些內(nèi)建機(jī)制來(lái)避免單個(gè)工作進(jìn)程的超載。
第二種方式是,主進(jìn)程建立監(jiān)聽嵌套字,并將它發(fā)送給感興趣的工作進(jìn)程,由工作進(jìn)程直接接受傳入連接。
第二種方式理論上有最好的性能。然而在實(shí)踐中,由于操作系統(tǒng)的調(diào)度變幻莫測(cè),分配往往十分不平衡。負(fù)載曾被觀測(cè)到超過(guò) 70% 的連接結(jié)束于總共八個(gè)進(jìn)程中的兩個(gè)。
因?yàn)?`server.listen()` 將大部分工作交給了主進(jìn)程,所以一個(gè)普通的node.js進(jìn)程和一個(gè)集群工作進(jìn)程會(huì)在三種情況下有所區(qū)別:
1. `server.listen({fd: 7})` 由于消息被傳遞到主進(jìn)程,**父進(jìn)程中的**文件描述符 7 會(huì)被監(jiān)聽,并且句柄會(huì)被傳遞給工作進(jìn)程,而不是監(jiān)聽工作進(jìn)程中文件描述符 7 所引用的東西。
1. `server.listen(handle)` 明確地監(jiān)聽一個(gè)句柄會(huì)使得工作進(jìn)程使用所給句柄,而不是與主進(jìn)程通訊。如果工作進(jìn)程已經(jīng)擁有了該句柄,則假定您知道您在做什么。
1. `server.listen(0)` 通常,這會(huì)讓服務(wù)器監(jiān)聽一個(gè)隨機(jī)端口。然而,在集群中,各個(gè)工作進(jìn)程每次 `listen(0)` 都會(huì)得到一樣的“隨機(jī)”端口。實(shí)際上,端口在第一次時(shí)是隨機(jī)的,但在那之后卻是可預(yù)知的。如果您想要監(jiān)聽一個(gè)唯一的端口,則請(qǐng)根據(jù)集群工作進(jìn)程 ID 來(lái)生成端口號(hào)。
由于在 Node.js 或您的程序中并沒有路由邏輯,工作進(jìn)程之間也沒有共享的狀態(tài),因此在您的程序中,諸如會(huì)話和登錄等功能應(yīng)當(dāng)被設(shè)計(jì)成不能太過(guò)依賴于內(nèi)存中的數(shù)據(jù)對(duì)象。
由于工作進(jìn)程都是獨(dú)立的進(jìn)程,因此它們會(huì)根據(jù)您的程序的需要被終止或重新派生,并且不會(huì)影響到其它工作進(jìn)程。只要還有工作進(jìn)程存在,服務(wù)器就會(huì)繼續(xù)接受連接。但是,Node 不會(huì)自動(dòng)為您管理工作進(jìn)程的數(shù)量,根據(jù)您的程序所需管理工作進(jìn)程池是您的責(zé)任。
### cluster.schedulingPolicy
調(diào)度策略 `cluster.SCHED_RR` 表示輪流制,`cluster.SCHED_NONE` 表示由操作系統(tǒng)處理。這是一個(gè)全局設(shè)定,并且一旦您派生了第一個(gè)工作進(jìn)程或調(diào)用了 `cluster.setupMaster()` 后便不可更改。
`SCHED_RR` 是除 Windows 外所有操作系統(tǒng)上的缺省方式。只要 libuv 能夠有效地分配 IOCP 句柄并且不產(chǎn)生巨大的性能損失,Windows 也將會(huì)更改為 `SCHED_RR` 方式。
`cluster.schedulingPolicy` 也可以通過(guò)環(huán)境變量 `NODE_CLUSTER_SCHED_POLICY` 設(shè)定。有效值為 `"rr"` 和 `"none"`。
### cluster.settings
- {Object}
- `exec` {String} 工作進(jìn)程文件的路徑。(缺省為 `__filename`)
- `args` {Array} 傳遞給工作進(jìn)程的字符串參數(shù)。(缺省為 `process.argv.slice(2)`)
- `silent` {Boolean} 是否將輸出發(fā)送到父進(jìn)程的 stdio。(缺省為 `false`)
所有由 `.setupMaster` 設(shè)定的設(shè)置都會(huì)儲(chǔ)存在此設(shè)置對(duì)象中。這個(gè)對(duì)象不應(yīng)由您手動(dòng)更改或設(shè)定。
### 集群的主進(jìn)程(判斷當(dāng)前進(jìn)程是否是主進(jìn)程)
- {Boolean}
如果進(jìn)程為主進(jìn)程則為 `true`。這是由 `process.env.NODE_UNIQUE_ID` 判斷的,如果 `process.env.NODE_UNIQUE_ID` 為 undefined,則 `isMaster` 為 `true`。
### 當(dāng)前進(jìn)程是否是從主進(jìn)程的fork出來(lái)的
- {Boolean}
如果當(dāng)前進(jìn)程是分支自主進(jìn)程的工作進(jìn)程,則該布爾標(biāo)識(shí)的值為 `true`。如果 `process.env.NODE_UNIQUE_ID` 被設(shè)定為一個(gè)值,則 `isWorker` 為 `true`。
### 事件: 'fork'
- `worker` {Worker object}
當(dāng)一個(gè)新的工作進(jìn)程被分支出來(lái),cluster 模塊會(huì)產(chǎn)生一個(gè) 'fork' 事件。這可被用于記錄工作進(jìn)程活動(dòng),以及創(chuàng)建您自己的超時(shí)判斷。
~~~
cluster.on('fork', function(worker) {
timeouts[worker.id] = setTimeout(errorMsg, 2000);
});
cluster.on('listening', function(worker, address) {
clearTimeout(timeouts[worker.id]);
});
cluster.on('exit', function(worker, code, signal) {
clearTimeout(timeouts[worker.id]);
errorMsg();
});
~~~
### 事件: 'online'
- `worker` {Worker object}
分支出一個(gè)新的工作進(jìn)程后,工作進(jìn)程會(huì)響應(yīng)一個(gè)在線消息。當(dāng)主進(jìn)程收到一個(gè)在線消息后,它會(huì)觸發(fā)該事件。'fork' 和 'online' 的區(qū)別在于前者發(fā)生于主進(jìn)程嘗試分支出工作進(jìn)程時(shí),而后者發(fā)生于工作進(jìn)程被執(zhí)行時(shí)。
~~~
cluster.on('online', function(worker) {
console.log("嘿嘿,工作進(jìn)程完成分支并發(fā)出回應(yīng)了");
});
~~~
### 事件: 'listening'
- `worker` {Worker object}
- `address` {Object}
當(dāng)工作進(jìn)程調(diào)用 `listen()` 時(shí),一個(gè) `listening` 事件會(huì)被自動(dòng)分配到服務(wù)器實(shí)例中。當(dāng)服務(wù)器處于監(jiān)聽時(shí),一個(gè)消息會(huì)被發(fā)送到那個(gè)'listening'事件被分發(fā)的主進(jìn)程。
事件處理器被執(zhí)行時(shí)會(huì)帶上兩個(gè)參數(shù)。其中 `worker` 包含了工作進(jìn)程對(duì)象,`address` 對(duì)象包含了下列連接屬性:地址 `address`、端口號(hào) `port` 和地址類型 `addressType`。如果工作進(jìn)程監(jiān)聽多個(gè)地址,那么這些信息將十分有用。
~~~
cluster.on('listening', function(worker, address) {
console.log("一個(gè)工作進(jìn)程剛剛連接到 " + address.address + ":" + address.port);
});
~~~
### 事件: 'disconnect'
- `worker` {Worker object}
當(dāng)一個(gè)工作進(jìn)程的 IPC 通道斷開時(shí)此事件會(huì)發(fā)生。這發(fā)生于工作進(jìn)程結(jié)束時(shí),通常是調(diào)用 `.kill()` 之后。
當(dāng)調(diào)用 `.disconnect()` 后,`disconnect` 和 `exit` 事件之間可能存在延遲。該事件可被用于檢測(cè)進(jìn)程是否被卡在清理過(guò)程或存在長(zhǎng)連接。
~~~
cluster.on('disconnect', function(worker) {
console.log('工作進(jìn)程 #' + worker.id + ' 斷開了連接');
});
~~~
### 事件: 'exit'
- `worker` {Worker object}
- `code` {Number} 如果是正常退出則為退出代碼。
- `signal` {String} 使得進(jìn)程被終止的信號(hào)的名稱(比如 `'SIGHUP'`)。
當(dāng)任意工作進(jìn)程被結(jié)束時(shí),集群模塊會(huì)分發(fā)`exit` 事件。通過(guò)再次調(diào)用`fork()`函數(shù),可以使用這個(gè)事件來(lái)重啟工作進(jìn)程。
~~~
cluster.on('exit', function(worker, code, signal) {
var exitCode = worker.process.exitCode;
console.log('工作進(jìn)程 ' + worker.process.pid + ' 被結(jié)束('+exitCode+')。正在重啟...');
cluster.fork();
});
~~~
### 事件: 'setup'
- `worker` {Worker object}
當(dāng) `.setupMaster()` 函數(shù)被執(zhí)行時(shí)觸發(fā)此事件。如果 `.setupMaster()` 在 `fork()` 之前沒被執(zhí)行,那么它會(huì)不帶參數(shù)調(diào)用 `.setupMaster()`。
### cluster.setupMaster([settings])
- `settings` {Object}
- `exec` {String} 工作進(jìn)程文件的路徑。(缺省為 `__filename`)
- `args` {Array} 傳給工作進(jìn)程的字符串參數(shù)。(缺省為 `process.argv.slice(2)`)
- `silent` {Boolean} 是否將輸出發(fā)送到父進(jìn)程的 stdio。(缺省為 `false`)
`setupMaster` 被用于更改缺省的 `fork` 行為。新的設(shè)置會(huì)立即永久生效,并且在之后不能被更改。
實(shí)例:
~~~
var cluster = require("cluster");
cluster.setupMaster({
exec : "worker.js",
args : ["--use", "https"],
silent : true
});
cluster.fork();
~~~
### cluster.fork([env])
- `env` {Object} 添加到子進(jìn)程環(huán)境變量中的鍵值對(duì)。
- 返回 {Worker object}
派生一個(gè)新的工作進(jìn)程。這個(gè)函數(shù)只能在主進(jìn)程中被調(diào)用。
### cluster.disconnect([callback])
- `callback` {Function} 當(dāng)所有工作進(jìn)程都斷開連接并且句柄被關(guān)閉時(shí)被調(diào)用
調(diào)用此方法時(shí),所有的工作進(jìn)程都會(huì)優(yōu)雅地將自己結(jié)束掉。當(dāng)它們都斷開連接時(shí),所有的內(nèi)部處理器都會(huì)被關(guān)閉,使得主進(jìn)程可以可以在沒有其它事件等待時(shí)優(yōu)雅地結(jié)束。
該方法帶有一個(gè)可選的回調(diào)參數(shù),會(huì)在完成時(shí)被調(diào)用。
### cluster.worker
- {Object}
對(duì)當(dāng)前工作進(jìn)程對(duì)象的引用。在主進(jìn)程中不可用。
~~~
if (cluster.isMaster) {
console.log('我是主進(jìn)程');
cluster.fork();
cluster.fork();
} else if (cluster.isWorker) {
console.log('我是工作進(jìn)程 #' + cluster.worker.id);
}
~~~
### cluster.workers
- {Object}
一個(gè)儲(chǔ)存活動(dòng)工作進(jìn)程對(duì)象的哈希表,以 `id` 字段作為主鍵。它能被用作遍歷所有工作進(jìn)程,僅在主進(jìn)程中可用。
~~~
// 遍歷所有工作進(jìn)程
function eachWorker(callback) {
for (var id in cluster.workers) {
callback(cluster.workers[id]);
}
}
eachWorker(function(worker) {
worker.send('向一線工作者們致以親切問(wèn)候!');
});
~~~
如果您希望通過(guò)通訊通道引用一個(gè)工作進(jìn)程,那么使用工作進(jìn)程的唯一標(biāo)識(shí)是找到那個(gè)工作進(jìn)程的最簡(jiǎn)單的辦法。
~~~
socket.on('data', function(id) {
var worker = cluster.workers[id];
});
~~~
### 類: Worker
一個(gè) Worker 對(duì)象包含了工作進(jìn)程的所有公開信息和方法??赏ㄟ^(guò)主進(jìn)程中的 `cluster.workers` 或工作進(jìn)程中的 `cluster.worker` 取得。
### worker.id
- {String}
每個(gè)新的工作進(jìn)程都被賦予一個(gè)唯一的標(biāo)識(shí),這個(gè)標(biāo)識(shí)被儲(chǔ)存在 `id` 中。
當(dāng)一個(gè)工作進(jìn)程可用時(shí),這就是它被索引在 cluster.workers 中的主鍵。
### worker.process
- {ChildProcess object}
所有工作進(jìn)程都是使用 `child_process.fork()` 創(chuàng)建的,該函數(shù)返回的對(duì)象被儲(chǔ)存在 process 中。
參考:[Child Process 模塊](#)
### worker.suicide
- {Boolean}
該屬性是一個(gè)布爾值。它會(huì)在工作進(jìn)程調(diào)用 `.kill()` 后終止時(shí)或調(diào)用 `.disconnect()` 方法時(shí)被設(shè)置。在此之前它的值是 `undefined`。
### worker.send(message, [sendHandle])
- `message` {Object}
- `sendHandle` {Handle object}
該函數(shù)等同于 `child_process.fork()` 提供的 send 方法。在主進(jìn)程中您可以用該函數(shù)向特定工作進(jìn)程發(fā)送消息。當(dāng)然,在工作進(jìn)程中您也能使用 `process.send(message)`,因?yàn)樗鼈兪峭粋€(gè)函數(shù)。
這個(gè)例子會(huì)回應(yīng)來(lái)自主進(jìn)程的所有消息:
~~~
} else if (cluster.isWorker) {
process.on('message', function(msg) {
process.send(msg);
});
}
~~~
### worker.kill([signal='SIGTERM'])
- `signal` {String} 發(fā)送給工作進(jìn)程的終止信號(hào)的名稱
該函數(shù)會(huì)終止工作進(jìn)程,并告知主進(jìn)程不要派生一個(gè)新工作進(jìn)程。布爾值 `suicide` 讓您區(qū)分自行退出和意外退出。
~~~
// 終止工作進(jìn)程
worker.kill();
~~~
該方法的別名是 `worker.destroy()`,以保持向后兼容。
### worker.disconnect()
調(diào)用該函數(shù)后工作進(jìn)程將不再接受新連接,但新連接仍會(huì)被其它正在監(jiān)聽的工作進(jìn)程處理。已存在的連接允許正常退出。當(dāng)沒有連接存在,連接到工作進(jìn)程的 IPC 通道會(huì)被關(guān)閉,以便工作進(jìn)程安全地結(jié)束。當(dāng) IPC 通道關(guān)閉時(shí) `disconnect` 事件會(huì)被觸發(fā),然后則是工作進(jìn)程最終結(jié)束時(shí)觸發(fā)的 `exit` 事件。
由于可能存在長(zhǎng)連接,通常會(huì)實(shí)現(xiàn)一個(gè)超時(shí)機(jī)制。這個(gè)例子會(huì)告知工作進(jìn)程斷開連接,并且在 2 秒后銷毀服務(wù)器。另一個(gè)備選方案是 2 秒后執(zhí)行 `worker.kill()`,但那樣通常會(huì)使得工作進(jìn)程沒有機(jī)會(huì)進(jìn)行必要的清理。
~~~
process.on('message', function(msg) {
if (msg === 'force kill') {
server.close();
}
});
}
~~~
### 事件: 'message'
- `message` {Object}
該事件和 `child_process.fork()` 所提供的一樣。在主進(jìn)程中您應(yīng)當(dāng)使用該事件,而在工作進(jìn)程中您也可以使用 `process.on('message')`。
舉個(gè)例子,這里有一個(gè)集群,使用消息系統(tǒng)在主進(jìn)程中統(tǒng)計(jì)請(qǐng)求的數(shù)量:
~~~
// 將請(qǐng)求通知主進(jìn)程
process.send({ cmd: 'notifyRequest' });
}).listen(8000);
}
~~~
### 事件: 'online'
和 `cluster.on('online')` 事件一樣,但僅當(dāng)特定工作進(jìn)程的狀態(tài)改變時(shí)發(fā)生。
~~~
cluster.fork().on('online', function() {
// 工作進(jìn)程在線
});
~~~
### 事件: 'listening'
- `address` {Object}
和 `cluster.on('listening')` 事件一樣,但僅當(dāng)特定工作進(jìn)程的狀態(tài)改變時(shí)發(fā)生。
~~~
cluster.fork().on('listening', function(address) {
// 工作進(jìn)程正在監(jiān)聽
});
~~~
### 事件: 'disconnect'
和 `cluster.on('disconnect')` 事件一樣,但僅當(dāng)特定工作進(jìn)程的狀態(tài)改變時(shí)發(fā)生。
~~~
cluster.fork().on('disconnect', function() {
// 工作進(jìn)程斷開了連接
});
~~~
### 事件: 'exit'
- `code` {Number} 如果是正常退出則為退出代碼。
- `signal` {String} 使得進(jìn)程被終止的信號(hào)的名稱(比如 `'SIGHUP'`)。
由單個(gè)工作進(jìn)程實(shí)例在底層子進(jìn)程被結(jié)束時(shí)觸發(fā)。詳見[子進(jìn)程事件: 'exit'](#)。
~~~
var worker = cluster.fork();
worker.on('exit', function(code, signal) {
if( signal ) {
console.log("工人被信號(hào) " + signal + " 殺掉了");
} else if( code !== 0 ) {
console.log("工作進(jìn)程退出,錯(cuò)誤碼:" + code);
} else {
console.log("勞動(dòng)者的勝利!");
}
});
~~~
- 關(guān)于本文檔
- 概述
- 斷言 (assert)
- Buffer
- Addons插件
- 子進(jìn)程
- 集群
- 控制臺(tái)
- 加密(Crypto)
- 調(diào)試器
- DNS
- 域
- 事件 (Events)
- File System
- 全局對(duì)象
- HTTP
- HTTPS
- Modules
- net
- 操作系統(tǒng)
- 路徑 (Path)
- process
- punycode
- Query String
- Readline
- REPL
- Smalloc
- 流
- 字符串解碼器
- 定時(shí)器
- TLS (SSL)
- TTY
- UDP / 數(shù)據(jù)報(bào)套接字
- URL
- utils
- 執(zhí)行 JavaScript
- Zlib
- 進(jìn)度
- 感謝
