在上一篇文章「Node JS 內部結構」中,我們討論了Node JS 內部架構,並討論了為什麼我們應該增加節點線程池大小以同時處理多個請求。我已經告訴過你,可擴展性和效能與線程池大小無關。
為了可擴展性和高效能,我們可以使用叢集和工作執行緒。
假設您正在參加一場盛大的婚禮,有數千位賓客參加婚禮。有一間廚房,一名廚師正在為所有這些客人準備食物。聽起來不可預測,對吧?如果您只有一名廚師,您就沒有充分利用廚房的全部資源。
這正是在多核心 CPU 上執行的 Node JS 應用程式中發生的情況,當僅使用一個核心來處理所有請求時。因此,即使我們的機器具有多核心的能力,如果沒有集群,我們的應用程式也只能在單核心上運行。一個核心負責處理所有工作。
當你的廚房裡有多個廚師正在工作時,這就是集群。
叢集是一種讓單一 Node JS 應用程式能夠有效利用多個 CPU 核心的技術。
要實作集群,您必須使用 Node JS 中的集群模組。
const cluster = require('cluster');
透過使用此叢集模組,您可以建立 Node JS 應用程式的多個實例。這些實例稱為工人。所有工作人員共用相同的伺服器連接埠並同時處理傳入請求。
叢集架構中有兩種類型的進程。
1.主流程:
Master進程就像廚房裡的主廚管理工人。它初始化應用程序,設定叢集環境,並將任務委託給工作進程。它不直接處理應用程式請求。
Master進程是做什麼的?
使用 cluster.fork() 方法建立多個工作進程。如果工作人員意外崩潰或退出,它也會重新啟動工作人員。
它確保傳入請求分佈在所有工作流程中。在 Linux 上,這是由作業系統處理的,在 Windows 上,Node JS 本身充當負載平衡器。
它可以透過IPC(進程間通訊)在worker之間進行通訊。
2.工作進程:
工作進程是主進程所建立的 Node JS 應用程式的實例。每個進程在單獨的 CPU 核心上獨立運行並處理傳入請求。
工作進程無法直接相互通信,它們透過主進程進行通訊。
工作進程處理傳入的請求並執行一些任務,例如資料庫查詢、計算或任何應用程式邏輯。
const cluster = require('cluster');
在這裡,我們先檢查這是主進程。如果是,那麼它將創建工作進程。
在我們的程式碼中,我使用 cluster.fork() 建立一個工作進程。
但是,這不是創建工作進程的理想方式。
假設您正在建立 4 個工作進程,而您的系統有兩個核心。
為了解決這個問題,而不是創建硬編碼的工作進程,先找到 CPU 核心,然後考慮資料創建工作進程。
const cluster = require('cluster'); const os = require('os'); if (cluster.isMaster) { console.log(`Master ${process.pid} is running`); // Fork workers cluster.fork(); cluster.fork(); cluster.fork(); cluster.fork(); } else { console.log(`Worker ${process.pid} is running`); // Worker logic (e.g., server setup) goes here }
我使用雙核心系統,所以輸出將如下所示。
const cluster = require('cluster'); const os = require('os'); if (cluster.isMaster) { console.log(`Master ${process.pid} is running`); const numCPUs = os.cpus().length; // Fork workers for (let i = 0; i < numCPUs; i++) { cluster.fork(); } } else { console.log(`Worker ${process.pid} is running`); // Worker logic (e.g., server setup) goes here }
現在,您有一個問題,如果您有雙核心 CPU,那麼為什麼要建立 4 個工作進程?
這是因為邏輯核心數為 4,我的 CPU 支援超執行緒或同步多執行緒 (SMT)。
在餐廳裡,服務生接受訂單並將訂單交給廚師團隊,因為烹飪需要一些時間。如果有清潔桌子或任何其他與服務員相關的工作,那麼服務員就會這樣做。當訂單準備好後,廚師將食物回饋給服務員,服務員將這些食物提供給顧客。
這與工作執行緒相關的場景相同。如果出現任何計算量大的任務,例如大規模資料處理、複雜計算或繁重演算法,則主執行緒會將此任務委託給工作執行緒。該任務由工作執行緒執行,而不是主執行緒。
*為什麼,這有幫助? *
我們知道 Node JS 事件循環是單執行緒的,如果這種繁重的運算工作由主執行緒完成,那麼事件循環將被阻塞。如果您使用這些工作線程,那麼這些繁重的任務將交給工作線程,工作線程執行這些任務,而不是主線程,因此事件循環不會被阻塞。
工作執行緒可以透過訊息傳遞系統與主執行緒通信,並且可以使用結構化克隆(深複製)在執行緒之間發送資料。
現在,我們正在嘗試模仿工作執行緒的工作。
main.js(主執行緒)
Master 12345 is running Worker 12346 is running Worker 12347 is running Worker 12348 is running Worker 12349 is running
worker.js(工作執行緒)
const { Worker } = require('worker_threads'); function startWorker() { const worker = new Worker('./worker.js'); // Create a worker using worker.js // Listen for messages from the worker worker.on('message', (message) => { console.log('Message from worker:', message); }); // Handle errors in the worker worker.on('error', (error) => { console.error('Worker error:', error); }); // Handle worker exit worker.on('exit', (code) => { console.log(`Worker exited with code ${code}`); }); // Send a message to the worker worker.postMessage({ num: 100 }); } startWorker();
如果資料包含大型結構,它將被深度克隆並傳遞,這可能會產生一些效能開銷。
程式碼的工作
Worker 類別用於產生新執行緒。
您可以使用worker.postMessage向worker發送數據,並使用worker.on('message',callback)監聽訊息。
在工作執行緒中,parentPort 是與主執行緒通訊的主要介面。
您可以監聽來自主執行緒(parentPort.on('message'))的訊息並使用parentPort.postMessage傳回訊息。
輸出將是:
const cluster = require('cluster');
現在,您還有一個問題:為什麼我們不建立數百個工作執行緒?
但是,原因是如果您建立的執行緒多於核心數量,執行緒將競爭 CPU 時間,導致上下文切換,這會導致成本高昂並降低整體效能。
什麼時候應該在 Node.js 中使用叢集、工作執行緒或兩者?
1。何時使用工作執行緒?
涉及大量計算的任務,例如影像/視訊處理、資料壓縮或加密、機器學習推理、科學計算
您需要在執行緒之間有效地共享資料而不重複資料。
如果您的應用程式只需要在單一進程內擴展,但仍需要 CPU 密集型任務的並行性。
2.什麼時候使用聚類?
任務涉及處理大量客戶端請求,例如 Web、伺服器、聊天應用程式和 API。叢集透過在所有 CPU 核心之間分配請求來幫助水平擴展。
您的應用程式不需要在進程之間共享大量資料。
您希望透過產生多個 Node.js 進程來利用所有可用的核心。
3.什麼時候同時使用叢集和工作執行緒?
應用程式處理 HTTP 請求,但卸載計算密集型任務。範例:網頁伺服器處理檔案上傳並執行影像大小調整或影片轉碼。
您需要進程級和執行緒級並行性才能實現高吞吐量。在電子商務網站中,叢集可確保多個進程處理傳入要求。工作執行緒處理後台任務,例如產生個人化推薦。
謝謝。
歡迎提出問題或提出任何建議。
如果您發現本文內容豐富,請按讚。
以上是叢集和工作線程 - Node JS的詳細內容。更多資訊請關注PHP中文網其他相關文章!