當前位置:
首頁 > 最新 > 走進Node.js之多進程模型

走進Node.js之多進程模型

文:正龍

本文原創,轉載請註明作者及出處

之前的文章「走進Node.js之HTTP實現分析」中,大家已經了解 Node.js 是如何處理 HTTP 請求的,在整個處理過程,它僅僅用到單進程模型。那麼如何讓 Web 應用擴展到多進程模型,以便充分利用CPU資源呢?答案就是 Cluster。本篇文章將帶著大家一起分析Node.js的多進程模型。

首先,來一段經典的 Node.js 主從服務模型代碼:

通常,主從模型包含一個主進程(master)和多個從進程(worker),主進程負責接收連接請求,以及把單個的請求任務分發給從進程處理;從進程的職責就是不斷響應客戶端請求,直至進入等待狀態。如圖 3-1 所示:

圍繞這段代碼,本文希望講述清楚幾個關鍵問題:

進程 fork 是如何完成的?

在 Node.js 中,cluster.fork 與 POSIX 的 fork 略有不同:雖然從進程仍舊是 fork 創建,但是並不會直接使用主進程的進程映像,而是調用系統函數 execvp 讓從進程使用新的進程映像。另外,每個從進程對應一個 Worker 對象,它有如下狀態:none、online、listening、dead和disconnected。

ChildProcess 對象主要提供進程的創建(spawn)、銷毀(kill)以及進程句柄引用計數管理(ref 與 unref)。在對Process對象(process_wrap.cc)進行封裝之外,它自身也處理了一些細節問題。例如,在方法 spawn 中,如果需要主從進程之間建立 IPC 管道,則通過環境變數 NODE_CHANNEL_FD 來告知從進程應該綁定的 IPC 相關的文件描述符(fd),這個特殊的環境變數後面會被再次涉及到。

以上提到的三個對象引用關係如下:

cluster.fork 的主要執行流程:

主進程在執行 cluster.fork 時,會指定兩個特殊的環境變數 NODE_CHANNEL_FD 和 NODE_UNIQUE_ID,所以從進程的初始化過程跟一般 Node.js 進程略有不同:

IPC實現細節

上文提到了 Node.js 主從進程僅僅通過 IPC 維持聯絡,那這一節就來深入分析下 IPC 的實現細節。首先,讓我們看一段示例代碼:

1-master.js

1-slave.js

node 1-master.js之後的運行結果如下:

細心的同學可能發現控制台輸出並不是連續的,master和slave的日誌交錯列印,這是由於並行進程執行順序不可預知造成的。

socketpair

前文提到從進程實際上通過系統調用 execvp 啟動新的 Node.js 實例;也就是說默認情況下,Node.js 主從進程不會共享文件描述符表,那它們到底是如何互發消息的呢?

原來,可以利用 socketpair 創建一對全雙工匿名 socket,用於在進程間互發消息;其函數簽名如下:

通常情況下,我們是無法通過 socket 來傳遞文件描述符的;當主進程與客戶端建立了連接,需要把連接描述符告知從進程處理,怎麼辦?其實,通過指定 socketpair 的第一個參數為 AF_UNIX,表示創建匿名 UNIX 域套接字(UNIX domain socket),這樣就可以使用系統函數 sendmsg 和 recvmsg 來傳遞/接收文件描述符了。

主進程在調用 cluster.fork 時,相關流程如下:

至此,主從進程就可以進行雙向通信了。流程圖如下:

我們再回看一下環境變數 NODE_CHANNEL_FD,令人疑惑的是,它的值始終為3。進程級文件描述符表中,0-2分別是標準輸入stdin、標準輸出stdout和標準錯誤輸出stderr,那麼可用的第一個文件描述符就是3,socketpair 顯然會佔用從進程的第一個可用文件描述符。這樣,當從進程往 fd=3 的流中寫入數據時,主進程就可以收到消息;反之,亦類似。

IPC 讀取消息主要是流操作,以後有機會詳解,下面列出主要流程:

涉及到的類圖關係如下:

伺服器主從模型

以上大概分析了從進程的創建過程及其特殊性;如果要實現主從服務模型的話,還需要解決一個基本問題:從進程怎麼獲取到與客戶端間的連接描述符?我們打算從 process.send(只有在從進程的全局 process 對象上才有 send 方法,主進程可以通過 worker.process 或 worker 訪問該方法)的函數簽名著手:

其參數 message 和 callback 含義也許顯而易見,分別指待發送的消息對象和操作結束之後的回調函數。那它的第二個參數 sendHandle 用途是什麼?

前文提到系統函數 socketpair 可以創建一對雙向 socket,能夠用來發送 JSON 消息,這一塊主要涉及到流操作;另外,當 sendHandle 有值時,它們還可以用於傳遞文件描述符,其過程要相對複雜一些,但是最終會調用系統函數 sendmsg 以及 recvmsg。

傳遞與客戶端的連接描述符

在主從服務模型下,主進程負責跟客戶端建立連接,然後把連接描述符通過 sendmsg 傳遞給從進程。我們來看看這一過程:

從進程

主進程

流程圖如下:

從進程上調用listen

客戶端連接處理

從進程如何與主進程監聽同一埠?

原因主要有兩點:

I. 從進程中 Node.js 運行時的初始化略有不同

II. listen 方法在主從進程中執行的代碼略有不同。

在 net.Server(net.js)的方法 listen 中,如果是主進程,則執行標準的埠綁定流程;如果是從進程,則會調用 cluster._getServer,參見上面對該方法的描述。

最後,附上基於libuv實現的一個 C 版 Master-Slave 服務模型,GitHub地址(https://github.com/Hujiang-FE/simple-http-server/tree/master/v2)。

啟動伺服器之後,訪問 http://localhost:3333 的運行結果如下:

相信通過本篇文章的介紹,大家已經對Node.js的Cluster有了一個全面的了解。下一次作者會跟大家一起深入分析Node.js進程管理在生產環境下的可用性問題,敬請期待。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 滬江技術學院 的精彩文章:

React Native 網路層分析

TAG:滬江技術學院 |