我們已經(jīng)知道Node.js是單線程的,這意味著它將在可能具有多個內(nèi)核的系統(tǒng)中使用處理器的單個內(nèi)核。盡管它可以很好地處理單線程系統(tǒng)的負(fù)載,但肯定有優(yōu)化的空間,集群模塊是這樣做的一種方式。
引入群集模塊是為了通過在多個處理器內(nèi)核上運行工作進(jìn)程/子進(jìn)程來擴展應(yīng)用程序執(zhí)行。這些進(jìn)程共享相同的服務(wù)器端口,但即使使用相同的端口,這些進(jìn)程在一天結(jié)束時也是獨立的進(jìn)程。因此,他們每個人都有自己的 V8 實例、事件循環(huán)、自己的內(nèi)存以及所有爵士樂。這些進(jìn)程使用 IPC(進(jìn)程間通信)通道與父進(jìn)程進(jìn)行通信。在學(xué)習(xí)本教程的過程中,我們將查看所有功能,因此讓我們深入研究一下。
我們試圖解決的問題
讓我們通過鍵入 來快速設(shè)置節(jié)點項目。(為了方便起見,我將添加快遞,但你不一定非要這樣做)。通過鍵入 來安裝快速和負(fù)載測試。我們將在本教程的后面部分使用 loadtest 來了解使用群集模塊獲得的性能優(yōu)勢。安裝后,創(chuàng)建一個名為 nonCluster 的文件.js并復(fù)制此代碼(此文件將包含不帶群集模塊的代碼,以便進(jìn)行比較)。npm init -ynpm i express loadtest
這是一個非常簡單的快速應(yīng)用程序,有2個請求。終結(jié)點執(zhí)行 CPU 密集型任務(wù),從而阻止事件循環(huán)。終結(jié)點會立即返回響應(yīng)。簡單!/heavy/light
現(xiàn)在運行服務(wù)器,先向終結(jié)點發(fā)出請求,然后再向終結(jié)點發(fā)出請求。終端節(jié)點顯然需要時間,但您會注意到終端節(jié)點也花費了相同的時間。這是因為端點占用了計算請求的時間,因此阻塞了事件循環(huán)。因此,在請求之后發(fā)出的任何請求現(xiàn)在都必須等待其完成。/heavy/light/heavy/light/heavy/heavy
問題的解決方案
因此,為了避免這種阻塞,讓我們添加集群模塊。在名為 cluster.js的新文件中,復(fù)制以下代碼:
讓我們來分解一下。最初,當(dāng)您有一個進(jìn)程時,它會解析所有傳入的請求?,F(xiàn)在我們使用的是群集模塊,將有 2 種類型的進(jìn)程。父/主進(jìn)程和子進(jìn)程。最初,當(dāng)服務(wù)器啟動時,它將啟動一組進(jìn)程。之后,每當(dāng)有人向服務(wù)器發(fā)出請求時,父進(jìn)程就會將請求定向到子進(jìn)程(主要以輪循機制方式)。然后,子進(jìn)程將最終解決該請求。
在塊內(nèi),我們將檢查當(dāng)前進(jìn)程是否是父進(jìn)程。群集模塊具有一個名為 的屬性,該屬性允許您知道當(dāng)前進(jìn)程是子進(jìn)程還是父進(jìn)程。如果是父進(jìn)程,則使用 fork 方法創(chuàng)建群集。(我們只啟動與系統(tǒng)中存在的內(nèi)核總數(shù)相等的進(jìn)程,以避免調(diào)度開銷。
如果它不是父進(jìn)程,則意味著它是一個子進(jìn)程。因此,這個子進(jìn)程現(xiàn)在實際上負(fù)責(zé)解析請求。它將具有所有API端點及其相應(yīng)的邏輯。ifisMaster
分叉新工作線程后,它將以事件進(jìn)行響應(yīng)。我們將在父級上偵聽此事件,以查看是否按預(yù)期創(chuàng)建了所有進(jìn)程。online
當(dāng)工作線程死亡時,群集模塊會發(fā)出一個事件。因此,為了不使我們的應(yīng)用程序停機,我們將在新進(jìn)程出現(xiàn)故障時分叉。這樣,我們將始終啟動并運行一組進(jìn)程,即使任何其他進(jìn)程因任何有意或無意的原因而關(guān)閉。exit
好吧,這看起來不錯。現(xiàn)在,如果運行此文件并發(fā)出相同的請求(首先向終結(jié)點發(fā)出請求,然后向終結(jié)點發(fā)出請求),則會看到它按預(yù)期工作,而不會阻塞事件循環(huán)。因此,添加一組進(jìn)程確實有幫助?,F(xiàn)在,讓我們使用負(fù)載測試包來執(zhí)行一些相對繁重的測試。/heavy/light
由于群集應(yīng)用程序已在運行,因此讓我們先對其進(jìn)行測試。鍵入以運行測試。(如果您沒有全局安裝負(fù)載測試,只需在命令開頭添加 npx 即可。loadtest -n 1000 -c 100
-n 表示我設(shè)置為 1000 的請求總數(shù)。
-c 代表并發(fā)。它基本上將模擬一個真實世界的環(huán)境,其中應(yīng)用程序同時從多個客戶端接收請求,在這種情況下,它將是100個同時客戶端。
這些是“群集”應(yīng)用程序的匯總結(jié)果。
現(xiàn)在,讓我們切換到“非群集”應(yīng)用程序,并再次運行相同的測試。
使用群集時,整體性能明顯有顯著提高。但有一個問題。這兩項測試都是針對端點的,這是一個 CPU 密集型操作。讓我們嘗試運行相同的測試,但這次是針對終結(jié)點。/heavy/light
驚喜,驚喜。在此示例中,使用群集時,我們的性能實際上相對較差。為什么?
你看,Node.js主要是為I/O操作設(shè)計的,在大多數(shù)情況下,它不會阻塞事件循環(huán)。因此,由于它知道如何處理這些類型的操作,因此添加額外的進(jìn)程并將請求路由到這些進(jìn)程中的每一個最終都是很可能不需要的開銷。但是,在 CPU 密集型阻塞操作的情況下,最好在確實需要群集的進(jìn)程之間拆分請求數(shù)。
因此,它最終歸結(jié)為您的應(yīng)用程序的設(shè)計目的。如果你有一個微服務(wù)架構(gòu),并且有一個特定的服務(wù)來處理 CPU 密集型操作,你可以為該特定服務(wù)啟動一個集群,其余的可以由你的單線程節(jié)點進(jìn)程處理。
所以,這就是你在 Node.js應(yīng)用程序中使用集群的方式。它有自己的一組警告,在將其添加到代碼庫之前,您需要了解它們。