本文首發於貓眼前端團隊公衆號,轉載請註明出處。前端
若是你們用 PM2 管理 Node.js 進程,會發現它支持一種 cluster mode。開啓 cluster mode 後,支持給 Node.js 建立多個進程。 若是將 cluster mode 下的 instances 設置爲 max 的話,它還會根據服務器的 CPU 核心數,來建立對應數量的 Node 進程。 node
PM2 其實利用的是 Node.js Cluster 模塊來實現的,這個模塊的出現就是爲了解決 Node.js 實例單線程運行,沒法利用多核 CPU 的優點而出現的。那麼,Cluster 內部又是如何工做的呢?多個進程間是如何通訊的?多個進程是如何監聽同一個端口的?Node.js 是如何將請求分發到各個進程上的?若是你對上述問題還不清楚,不妨接着往下看。算法
Node.js worker 進程由child_process.fork()
方法建立,這也意味存在着父進程和多個子進程。代碼大體是這樣:編程
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
for (var i = 0, n = os.cpus().length; i < n; i += 1) {
cluster.fork();
}
} else {
// 啓動程序
}
複製代碼
學過操做系統的同窗,應該對 fork() 這個系統調用不陌生,調用它的進程爲父進程,fork 出來的都是子進程。子進程和父進程具備相同的代碼段、數據段、堆棧,可是它們的內存空間不共享。父進程(即 master 進程)負責監聽端口,接收到新的請求後將其分發給下面的 worker 進程。這裏涉及三個問題:父子進程通訊、負載均衡策略以及多進程的端口監聽。bash
備註:Linux 上 fork() 支持寫時複製,只有進程空間的各段的內容要發生變化時,纔會將父進程的內容複製一份給子進程。所以子進程和父進程一開始是共享相同的內存空間。服務器
master 進程經過 process.fork() 建立子進程,他們之間經過 IPC (內部進程通訊)通道實現通訊。操做系統的進程間通訊方式主要有如下幾種:架構
Node.js 爲父子進程的通訊提供了事件機制來傳遞消息。下面的例子實現了父進程將 TCP server 對象句柄傳給子進程。併發
const subprocess = require('child_process').fork('subprocess.js');
// 開啓 server 對象,併發送該句柄。
const server = require('net').createServer();
server.on('connection', (socket) => {
socket.end('被父進程處理');
});
server.listen(1337, () => {
subprocess.send('server', server);
});
複製代碼
process.on('message', (m, server) => {
if (m === 'server') {
server.on('connection', (socket) => {
socket.end('被子進程處理');
});
}
});
複製代碼
那麼問題又來了,若是進程間沒有父子關係,換句話說,咱們應該如何實現任意進程間的通訊呢?你們能夠去看看這篇文章:進程間通訊的另類實現負載均衡
前面提到,全部請求是經過 master 進程分配的,要保證服務器負載比較均衡的分配到各個 worker 進程上,這就涉及到負載均衡策略了。Node.js 默認採用的策略是 round-robin 時間片輪轉法。socket
round-robin 是一種很常見的負載均衡算法,Nginx 上也採用了它做爲負載均衡策略之一。它的原理很簡單,每一次把來自用戶的請求輪流分配給各個進程,從 1 開始,直到 N(worker 進程個數),而後從新開始循環。這個算法的問題在於,它是假定各個進程或者說各個服務器的處理性能是同樣的,可是若是請求處理間隔較長,就容易致使出現負載不均衡。所以咱們一般在 Nginx 上採用另外一種算法:WRR,加權輪轉法。經過給各個服務器分配必定的權重,每次選出權重最大的,給其權重減 1,直到權重所有爲 0 後,按照此時生成的序列輪詢。
能夠經過設置 NODE_CLUSTER_SCHED_POLICY 環境變量,或者經過 cluster.setupMaster(options) 來修改負載均衡策略。讀到這裏你們能夠發現,咱們能夠 Nginx 作多機器集羣上的負載均衡,而後用 Node.js Cluster 來實現單機多進程上的負載均衡。
最初的 Node.js 上,多個進程監聽同一個端口,它們相互競爭新 accept 過來的鏈接。這樣會致使各個進程的負載很不均衡,因而後來使用了上文提到的 round-robin 策略。具體思路是,master 進程建立 socket,綁定地址並進行監聽。該 socket 的 fd 不傳遞到各個 worker 進程。當 master 進程獲取到新的鏈接時,再決定將 accept 到的客戶端鏈接分發給指定的 worker 處理。簡單說就是,master 進程監聽端口,而後將鏈接經過某種分發策略(好比 round-robin),轉發給 worker 進程。這樣因爲只有 master 進程接收客戶端鏈接,就解決了競爭致使的負載不均衡的問題。可是這樣設計就要求 master 進程的穩定性足夠好了。
本文以 PM2 的 Cluster Mode 做爲切入點,向你們介紹了 Node.js Cluster 實現多進程的核心原理。重點講了進程通訊、負載均衡以及多進程端口監聽三個方面。經過研究 cluster 模塊能夠發現,不少底層原理或者是算法,其實都是通用的。好比 round-robin 算法,它在操做系統底層的進程調度中也有使用;好比 master-worker 這種架構,是否是在 Nginx 的多進程架構中也似曾相識;好比信號量、管道這些機制,也能夠在各類編程模式中見到它們的身影。當下市面上各類新技術層出不窮,但核心實際上是萬變不離其宗,理解了這些最基礎的知識,剩下的也能夠舉一反三了。
參考連接: