?
本文檔使用 php中文網(wǎng)手冊(cè) 發(fā)布
穩(wěn)定性: 2 - 不穩(wěn)定
單個(gè) Node 實(shí)例運(yùn)行在一個(gè)線程中。為了更好的利用多核系統(tǒng)的能力,可以啟動(dòng) Node 集群來(lái)處理負(fù)載。
在集群模塊里很容易就能創(chuàng)建一個(gè)共享所有服務(wù)器接口的進(jìn)程。
var cluster = require('cluster'); var http = require('http'); var numCPUs = require('os').cpus().length; if (cluster.isMaster) { // Fork workers. for (var i = 0; i < numCPUs; i++) { cluster.fork(); } cluster.on('exit', function(worker, code, signal) { console.log('worker ' + worker.process.pid + ' died'); }); } 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); }
運(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è)特性是最近才引入的,大家可以試試并提供反饋。
還要注意,在 Windows 系統(tǒng)里還不能在工作進(jìn)程中創(chuàng)建一個(gè)被命名的管道服務(wù)器。
child_process.fork
方法派生工作進(jìn)程,所以它能通過(guò) IPC 和父進(jìn)程通訊,并相互傳遞句柄。
集群模塊通過(guò)2種分發(fā)模式來(lái)處理連接。
第一種(默認(rèn)方法,除了 Windows 平臺(tái))為循環(huán)式。主進(jìn)程監(jiān)聽(tīng)一個(gè)端口,接收新的連接,再輪流的分發(fā)給工作進(jìn)程。
第二種,主進(jìn)程監(jiān)聽(tīng) socket,并發(fā)送給感興趣的工作進(jìn)程,工作進(jìn)程直接接收連接。
第二種方法理論上性能最高。實(shí)際上,由于操作系統(tǒng)各式各樣,分配往往分配不均。列如,70%的連接終止于2個(gè)進(jìn)程,實(shí)際上共有8個(gè)進(jìn)程。
因?yàn)?server.listen()
將大部分工作交給了主進(jìn)程,所以一個(gè)普通的 Node.js 進(jìn)程和一個(gè)集群工作進(jìn)程會(huì)在三種情況下有所區(qū)別:
server.listen({fd: 7})
由于消息被傳回主進(jìn)程,所以將會(huì)監(jiān)聽(tīng)主進(jìn)程里的文件描述符,而不是其他工作進(jìn)程里的文件描述符 7。
server.listen(handle)
監(jiān)聽(tīng)一個(gè)明確地句柄,會(huì)使得工作進(jìn)程使用指定句柄,而不是與主進(jìn)程通訊。如果工作進(jìn)程已經(jīng)擁有了該句柄,前提是您知道在做什么。
server.listen(0)
通常它會(huì)讓服務(wù)器隨機(jī)監(jiān)聽(tīng)端口。然而在集群里每個(gè)工作進(jìn)程 listen(0)
時(shí)會(huì)收到相同的端口。實(shí)際上僅第一次是隨機(jī)的,之后是可預(yù)測(cè)的。如果你想監(jiān)聽(tīng)一個(gè)特定的端口,可以根據(jù)集群的工作進(jìn)程的ID生產(chǎn)一個(gè)端口ID 。
在 Node.js 或你的程序里沒(méi)有路由邏輯,工作進(jìn)程見(jiàn)也沒(méi)有共享狀態(tài)。因此,像登錄和會(huì)話這樣的工作,不要設(shè)計(jì)成過(guò)度依賴內(nèi)存里的對(duì)象。
因?yàn)楣ぷ骶€程都是獨(dú)立的,你可以根據(jù)需求來(lái)殺死或者派生而不會(huì)影響其他進(jìn)程。只要仍然有工作進(jìn)程,服務(wù)器還會(huì)接收連接。Node 不會(huì)自動(dòng)管理工作進(jìn)程的數(shù)量,這是你的責(zé)任,你可以根據(jù)自己需求來(lái)管理。
調(diào)度策略 cluster.SCHED_RR
表示輪流制,cluster.SCHED_NONE
表示操作系統(tǒng)處理。這是全局性的設(shè)定,一旦你通過(guò) cluster.setupMaster()
派生了第一個(gè)工作進(jìn)程,它就不可更改了。
SCHED_RR
是除 Windows 外所有系統(tǒng)的默認(rèn)設(shè)置。只要 libuv 能夠有效地分配 IOCP 句柄并且不產(chǎn)生巨大的性能損失,Windows 也會(huì)改為 SCHED_RR 方式。
cluster.schedulingPolicy
也可通過(guò)環(huán)境變量 NODE_CLUSTER_SCHED_POLICY
來(lái)更改。有效值為 "rr"
和 "none"
。
{Object}
execArgv
{Array} 傳給可執(zhí)行的 Node 的參數(shù)列表(默認(rèn)=process.execArgv
)
exec
{String} 執(zhí)行文件的路徑。 (默認(rèn)=process.argv[1]
)
args
{Array} 傳給工作進(jìn)程的參數(shù)列表(默認(rèn)=process.argv.slice(2)
)
silent
{Boolean}是否將輸出發(fā)送給父進(jìn)程的 stdio。
(默認(rèn)=false
)
uid
{Number} 設(shè)置用戶進(jìn)程的ID。 (See setuid(2)。)
gid
{Number} 設(shè)置進(jìn)程組的ID。 (See setgid(2)。)
調(diào)用 .setupMaster()
(或 .fork()
) 方法后,這個(gè) settings 對(duì)象會(huì)包含設(shè)置內(nèi)容,包括默認(rèn)值。
設(shè)置后會(huì)立即凍結(jié),因?yàn)?code>.setupMaster()只能調(diào)用一次。
這個(gè)對(duì)象不應(yīng)該被手動(dòng)改變或設(shè)置。
{Boolean}
如果是主進(jìn)程,返回 true。如果 process.env.NODE_UNIQUE_ID
未定義,isMaster
為 true
。
{Boolean}
如果不是主進(jìn)程返回 true (和 cluster.isMaster
相反)。
worker
{Worker object}
當(dāng)一個(gè)新的工作進(jìn)程被分支出來(lái),集群模塊會(huì)產(chǎn)生 'fork' 事件。它可用于記錄工作進(jìn)程,并創(chuàng)建自己的超時(shí)管理。
var timeouts = []; function errorMsg() { console.error("Something must be wrong with the connection ..."); } 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(); });
worker
{Worker object}
分支出一個(gè)新的工作進(jìn)程后,它會(huì)響應(yīng)在線消息。當(dāng)主線程接收到在線消息后,它會(huì)觸發(fā)這個(gè)事件。'fork' 和 'online' 之間的區(qū)別在于,主進(jìn)程分支一個(gè)工作進(jìn)程后會(huì)調(diào)用 fork,而工作進(jìn)程運(yùn)行后會(huì)調(diào)用 emitted。
cluster.on('online', function(worker) { console.log("Yay, the worker responded after it was forked"); });
worker
{Worker object}
address
{Object}
工作進(jìn)程調(diào)用 listen()
時(shí),服務(wù)器會(huì)觸發(fā)'listening'事件,同時(shí)也會(huì)在主進(jìn)程的集群里觸發(fā)。
事件處理函數(shù)有兩個(gè)參數(shù),worker
包含工作進(jìn)程對(duì)象,address
包含以下屬性:address
, port
和 addressType
。如果工作進(jìn)程監(jiān)聽(tīng)多個(gè)地址的時(shí)候,這些東西非常有用。
cluster.on('listening', function(worker, address) { console.log("A worker is now connected to " + address.address + ":" + address.port); });
addressType
是以下內(nèi)容:
4
(TCPv4)
6
(TCPv6)
-1
(unix domain socket)
"udp4"
or "udp6"
(UDP v4 or v6)*
worker
{Worker object}
當(dāng)一個(gè)工作進(jìn)程的 IPC 通道關(guān)閉時(shí)會(huì)觸發(fā)這個(gè)事件。當(dāng)工作進(jìn)程正常退出,被殺死,或者手工關(guān)閉(例如worker.disconnect())時(shí)會(huì)調(diào)用。
disconnect
和 exit
事件間可能存在延遲。 這些事件可以用來(lái)檢測(cè)進(jìn)程是否卡在清理過(guò)程中,或者存在長(zhǎng)連接。
cluster.on('disconnect', function(worker) { console.log('The worker #' + worker.id + ' has disconnected'); });
worker
{Worker object}
code
{Number} 如果正常退出,則為退出代碼.
signal
{String} 使得進(jìn)程被殺死的信號(hào)名 (比如. 'SIGHUP'
)
當(dāng)任意一個(gè)工作進(jìn)程終止的時(shí)候,集群模塊會(huì)觸發(fā) 'exit' 事件。
可以調(diào)用 .fork()
重新啟動(dòng)工作進(jìn)程。
cluster.on('exit', function(worker, code, signal) { console.log('worker %d died (%s). restarting...', worker.process.pid, signal || code); cluster.fork(); });
參見(jiàn) <a rel="nofollow" href="https://nodejs.org/api/child_process.html#child_process_event_exit"">child_process event: 'exit'.
settings
{Object}
調(diào)用.setupMaster()
后會(huì)被觸發(fā)。
settings
對(duì)象就是 cluster.settings
對(duì)象。
詳細(xì)內(nèi)容參見(jiàn) cluster.settings
。
settings
{Object}
exec
{String} 執(zhí)行文件的路徑。 (默認(rèn)=process.argv[1]
)
args
{Array}傳給工作進(jìn)程的參數(shù)列表(默認(rèn)=process.argv.slice(2)
)
silent
{Boolean} 是否將輸出發(fā)送給父進(jìn)程的 stdio.
setupMaster
用來(lái)改變默認(rèn)的 'fork' 。 一旦調(diào)用,settings值將會(huì)出現(xiàn)在cluster.settings
里。
注意:
改變?nèi)魏卧O(shè)置,僅會(huì)對(duì)未來(lái)的工作進(jìn)程產(chǎn)生影響,不會(huì)影響對(duì)目前已經(jīng)運(yùn)行的進(jìn)程
工作進(jìn)程里,僅能改變傳遞給 .fork()
的 env
屬性。
以上的默認(rèn)值,僅在第一次調(diào)用的時(shí)候有效,之后的默認(rèn)值是調(diào)用 cluster.setupMaster()
后的值。
例如:
var cluster = require('cluster'); cluster.setupMaster({ exec: 'worker.js', args: ['--use', 'https'], silent: true }); cluster.fork(); // https worker cluster.setupMaster({ args: ['--use', 'http'] }); cluster.fork(); // http worker
僅能在主進(jìn)程里調(diào)用。
env
{Object} 添加到子進(jìn)程環(huán)境變量中的鍵值。
return {Worker object}
派生一個(gè)新的工作進(jìn)程。
僅能在主進(jìn)程里調(diào)用。
callback
{Function} 當(dāng)所有工作進(jìn)程都斷開(kāi)連接,并且關(guān)閉句柄后被調(diào)用。
cluster.workers
里的每個(gè)工作進(jìn)程可調(diào)用 .disconnect()
關(guān)閉。
關(guān)閉所有的內(nèi)部句柄連接,并且沒(méi)有任何等待處理的事件時(shí),允許主進(jìn)程優(yōu)雅的退出。
這個(gè)方法有一個(gè)可選參數(shù),會(huì)在完成時(shí)被調(diào)用。
僅能在主進(jìn)程里調(diào)用。
{Object}
對(duì)當(dāng)前工作進(jìn)程對(duì)象的引用。主進(jìn)程中不可用。
var 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); }
{Object}
存儲(chǔ)活躍工作對(duì)象的哈希表,主鍵是 id
,能方便的遍歷所有工作進(jìn)程,僅在主進(jìn)程可用。
當(dāng)工作進(jìn)程關(guān)閉連接并退出后,將會(huì)從 cluster.workers 里移除。這兩個(gè)事件的次序無(wú)法確定,僅能保證從cluster.workers 移除會(huì)發(fā)生在 'disconnect'
或 'exit'
之后。
// Go through all workers function eachWorker(callback) { for (var id in cluster.workers) { callback(cluster.workers[id]); } } eachWorker(function(worker) { worker.send('big announcement to all workers'); });
如果希望通過(guò)通訊通道引用工作進(jìn)程,那么使用工作進(jìn)程的 id 來(lái)查詢最簡(jiǎn)單。
socket.on('data', function(id) { var worker = cluster.workers[id]; });
一個(gè) Worker 對(duì)象包含工作進(jìn)程所有公開(kāi)的信息和方法。在主進(jìn)程里可用通過(guò) cluster.workers
來(lái)獲取,在工作進(jìn)程里可以通過(guò) cluster.worker
來(lái)獲取。
{String}
每一個(gè)新的工作進(jìn)程都有獨(dú)立的唯一標(biāo)示,它就是 id
。
當(dāng)工作進(jìn)程可用時(shí),id
就是 cluster.workers 里的主鍵。
{ChildProcess object}
所有工作進(jìn)程都是通用 child_process.fork() 創(chuàng)建的,該函數(shù)返回的對(duì)象被儲(chǔ)存在 process 中。
參見(jiàn): Child Process module
注意,當(dāng) process
和 .suicide
不是 true
的時(shí)候,會(huì)觸發(fā) 'disconnect'
事件,并使得工作進(jìn)程調(diào)用 process.exit(0)
。它會(huì)保護(hù)意外的連接關(guān)閉。
{Boolean}
調(diào)用 .kill()
或 .disconnect()
后設(shè)置,在這之前是 undefined
。
worker.suicide
能讓你區(qū)分出是自愿的還是意外退出,主進(jìn)程可以根據(jù)這個(gè)值,來(lái)決定是否是重新派生成工作進(jìn)程。
cluster.on('exit', function(worker, code, signal) { if (worker.suicide === true) { console.log('Oh, it was just suicide\' – no need to worry'). } }); // kill worker worker.kill();
message
{Object}
sendHandle
{Handle object}
這個(gè)函數(shù)和 child_process.fork() 提供的 send 方法相同。主進(jìn)程里你必須使用這個(gè)函數(shù)給指定工作進(jìn)程發(fā)消息。
在工作進(jìn)程里,你也可以用 process.send(message)
。
這個(gè)例子會(huì)回應(yīng)所有來(lái)自主進(jìn)程的消息:
if (cluster.isMaster) { var worker = cluster.fork(); worker.send('hi there'); } else if (cluster.isWorker) { process.on('message', function(msg) { process.send(msg); }); }
signal
{String}發(fā)送給工作進(jìn)程的殺死信號(hào)的名稱
這個(gè)函數(shù)會(huì)殺死工作進(jìn)程。在主進(jìn)程里,它會(huì)關(guān)閉 worker.process
,一旦關(guān)閉會(huì)發(fā)送殺死信號(hào)。在工作進(jìn)程里,關(guān)閉通道,退出,返回代碼0
。
會(huì)導(dǎo)致 .suicide
被設(shè)置。
為了保持兼容性,這個(gè)方法的別名是worker.destroy()
。
注意,在工作進(jìn)程里有process.kill()
,于此不同。
在工作進(jìn)程里,這個(gè)函數(shù)會(huì)關(guān)閉所有服務(wù)器,等待 'close' 事件,關(guān)閉 IPC 通道。
在主進(jìn)程里,發(fā)給工作進(jìn)程一個(gè)內(nèi)部消息,用來(lái)調(diào)用.disconnect()
會(huì)導(dǎo)致 .suicide
被設(shè)置。
注意,服務(wù)器關(guān)閉后,不再接受新的連接,但可以接受新的監(jiān)聽(tīng)。已經(jīng)存在的連接允許正常退出。當(dāng)連接為空得時(shí)候,工作進(jìn)程的 IPC 通道運(yùn)行優(yōu)雅的退出。
以上僅能適用于服務(wù)器的連接,客戶端的連接由工作進(jìn)程關(guān)閉。
注意,在工作進(jìn)程里,存在 process.disconnect
,但并不是這個(gè)函數(shù),它是 disconnect。
由于長(zhǎng)連接可能會(huì)阻塞進(jìn)程關(guān)閉連接,有一個(gè)較好的辦法是發(fā)消息給應(yīng)用,這樣應(yīng)用會(huì)想辦法關(guān)閉它們。超時(shí)管理也是不錯(cuò),如果超過(guò)一定時(shí)間后還沒(méi)有觸發(fā) disconnect
事件,將會(huì)殺死進(jìn)程。
if (cluster.isMaster) { var worker = cluster.fork(); var timeout; worker.on('listening', function(address) { worker.send('shutdown'); worker.disconnect(); timeout = setTimeout(function() { worker.kill(); }, 2000); }); worker.on('disconnect', function() { clearTimeout(timeout); }); } else if (cluster.isWorker) { var net = require('net'); var server = net.createServer(function(socket) { // connections never end }); server.listen(8000); process.on('message', function(msg) { if(msg === 'shutdown') { // initiate graceful close of any connections to server } }); }
工作進(jìn)程結(jié)束,返回 true
, 否則返回 false
。
當(dāng)工作進(jìn)程通過(guò) IPC 通道連接主進(jìn)程時(shí),返回 true
,否則 false
。工作進(jìn)程創(chuàng)建后會(huì)連接到主進(jìn)程。當(dāng)disconnect
事件觸發(fā)后會(huì)關(guān)閉連接。
message
{Object}
該事件和 child_process.fork()
所提供的一樣。在主進(jìn)程中您應(yīng)當(dāng)使用該事件,而在工作進(jìn)程中您也可以使用 process.on('message')
。
例如,有一個(gè)集群使用消息系統(tǒng)在主進(jìn)程中統(tǒng)計(jì)請(qǐng)求的數(shù)量:
var cluster = require('cluster'); var http = require('http'); if (cluster.isMaster) { // Keep track of http requests var numReqs = 0; setInterval(function() { console.log("numReqs =", numReqs); }, 1000); // Count requestes function messageHandler(msg) { if (msg.cmd && msg.cmd == 'notifyRequest') { numReqs += 1; } } // Start workers and listen for messages containing notifyRequest var numCPUs = require('os').cpus().length; for (var i = 0; i < numCPUs; i++) { cluster.fork(); } Object.keys(cluster.workers).forEach(function(id) { cluster.workers[id].on('message', messageHandler); }); } else { // Worker processes have a http server. http.Server(function(req, res) { res.writeHead(200); res.end("hello world\n"); // notify master about the request process.send({ cmd: 'notifyRequest' }); }).listen(8000); }
和 cluster.on('online')
事件類似, 僅能在特定工作進(jìn)程里觸發(fā)。
cluster.fork().on('online', function() { // Worker is online });
不會(huì)在工作進(jìn)程里觸發(fā)。
address
{Object}
和 cluster.on('listening')
事件類似, 僅能在特定工作進(jìn)程里觸發(fā)。
cluster.fork().on('listening', function(address) { // Worker is listening });
不會(huì)在工作進(jìn)程里觸發(fā)。
和 cluster.on('disconnect')
事件類似, 僅能在特定工作進(jìn)程里觸發(fā)。
cluster.fork().on('disconnect', function() { // Worker has disconnected });
code
{Number} 正常退出時(shí)的退出代碼.
signal
{String} 使得進(jìn)程被終止的信號(hào)的名稱(比如 SIGHUP
)。
和cluster.on('exit')
事件類似, 僅能在特定工作進(jìn)程里觸發(fā)。
var worker = cluster.fork(); worker.on('exit', function(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!"); } });
和 child_process.fork()
事件類似。
工作進(jìn)程里,你也可以用 process.on('error')
。