cluster
模塊是 Node.js 提供的一個用于多進程的模塊,它可以輕松地創建一組共享同一個服務器端口的子進程(worker進程)。通過使用 cluster
模塊,可以充分利用多核系統,提高應用程序的性能和可靠性。
基本原理
cluster
模塊基于 child_process
模塊,但它專門設計用于創建多個 Node.js 進程(worker進程),這些進程可以共享同一個服務器端口。它通過主進程(主進程)和工作進程的模型來實現這一點:
- 主進程:負責管理所有的工作進程。
- 工作進程:處理實際的客戶端請求。
主進程可以根據需要啟動和管理多個工作進程。每個工作進程都是一個獨立的 Node.js 進程。
注意關于連接:實際cluster module支持兩種連接處理方式。
- 一種是由master監聽并分發給worker,拿到worker結果后在返回給請求端。 這樣master和請求端之間是http通信,master和worker之間是IPC通信,master類似一個agent,用于連接請求端和worker之間。(注:進程間通信(IPC?- Interprocess communication)
- 一種是master監聽后通知相關worker,worker直接連接connection到請求端進行交互。
第二種看起來性能更好,但在實際中由于系統調度等原因表現不佳,無法做到很好的負載,所以一般用第一種。(refs: https://nodejs.org/api/cluster.html#cluster_how_it_works)
使用 cluster
模塊的基本步驟
- 檢查是否是主進程:使用
cluster.isMaster
或cluster.isPrimary
檢查當前進程是否是主進程。 - 主進程創建工作進程:如果是主進程,創建工作進程。
- 工作進程執行服務器代碼:如果是工作進程,執行服務器代碼。
以下是一個示例,展示如何使用 cluster
模塊創建一個簡單的 HTTP 服務器,該服務器可以使用多個工作進程來處理請求:
const cluster = require("node:cluster");
const http = require("node:http");
const numCPUs = require("node:os").cpus().length;if (cluster.isPrimary) {console.log(`主進程 ${process.pid} 正在運行`);// 根據 CPU 核心數創建工作進程for (let i = 0; i < numCPUs; i++) {cluster.fork();}cluster.on("exit", (worker, code, signal) => {console.log(`工作進程 ${worker.process.pid} 已退出`);});
} else {// 工作進程可以共享任何 TCP 連接// 在本例中是一個 HTTP 服務器http.createServer((req, res) => {let workerPid = process.pid;res.writeHead(200);res.end(`hello world, I am worker ${workerPid}\\n`);}).listen(8000);console.log(`工作進程 ${process.pid} 已啟動`);
}
cluster優勢
- 性能提升:通過使用多個工作進程,可以充分利用多核 CPU,提升應用的并發處理能力。
- 可靠性:如果一個工作進程崩潰,不會影響其他工作進程,主進程可以重啟崩潰的工作進程。
- 負載均衡:多個工作進程可以共享同一個服務器端口,自動實現負載均衡。
總結
cluster
模塊提供了一種簡單而強大的方式來實現 Node.js 應用的多進程處理,從而提高性能和可靠性。通過主進程和工作進程的模型,可以輕松地創建和管理多個工作進程,并讓它們共享同一個服務器端口以處理高并發請求。
最后思考
最后留個思考問題,大家有沒想過對于http服務,比如下面這個啟動http server監聽8000端口, nodejs cluster 里面可以起多個子進程, 但是如果分別起2個進程監聽相同端口 比如下面這個代碼連續執行兩次,則后啟動的進程會提示端口被占用,這是為什么 ?
有知道的小伙伴歡迎評論區留言,也歡迎大家多多點贊,點贊過20個,公布答案。
// httpServer.js
const http = require("node:http");
http.createServer((req, res) => {let workerPid = process.pid;res.writeHead(200);res.end(`hello world, I am worker ${workerPid}\n`);}).listen(8000);