核心要點
Node.js作為服務器端運行環境越來越受歡迎,尤其是在高流量網站方面,統計數據也證明了這一點。此外,眾多框架的可用性使其成為快速原型設計的良好環境。 Node.js具有事件驅動的架構,利用非阻塞I/O API允許異步處理請求。 Node.js的一個重要且經常被忽視的特性是其可擴展性。事實上,這是某些大型公司在其平台中集成Node.js(例如Microsoft、Yahoo、Uber和Walmart)甚至將其服務器端操作完全遷移到Node.js(例如PayPal、eBay和Groupon)的主要原因。每個Node.js進程都在單個線程中運行,默認情況下,32位系統的內存限制為512MB,64位系統的內存限制為1GB。儘管可以在32位系統上將內存限制提高到約1GB,在64位系統上提高到約1.7GB,但內存和處理能力仍然可能成為各種進程的瓶頸。 Node.js為擴展應用程序提供的優雅解決方案是將單個進程拆分為多個進程或Node.js術語中的工作進程。這可以通過集群模塊實現。集群模塊允許您創建子進程(工作進程),這些子進程與主Node進程(主進程)共享所有服務器端口。在本文中,您將了解如何創建Node.js集群以加快應用程序的速度。
集群是在父Node進程下運行的類似工作進程的池。工作進程使用child_processes模塊的fork()方法生成。這意味著工作進程可以共享服務器句柄並使用IPC(進程間通信)與父Node進程通信。主進程負責啟動和控制工作進程。您可以在主進程中創建任意數量的工作進程。此外,請記住,默認情況下,傳入連接在工作進程之間以輪詢方式分配(Windows除外)。實際上,還有另一種分配傳入連接的方法,這裡我不會討論,它將分配交給操作系統(Windows中的默認設置)。 Node.js文檔建議使用默認的輪詢樣式作為調度策略。儘管從理論上講,使用集群模塊聽起來很複雜,但其實現非常簡單。要開始使用它,您必須將其包含在您的Node.js應用程序中:
var cluster = require('cluster');
集群模塊多次執行相同的Node.js進程。因此,您需要做的第一件事是確定哪一部分代碼用於主進程,哪一部分代碼用於工作進程。集群模塊允許您如下識別主進程:
if(cluster.isMaster) { ... }
主進程是您啟動的進程,它又會初始化工作進程。要在主進程中啟動工作進程,我們將使用fork()方法:
cluster.fork();
此方法返回一個worker對象,其中包含有關已派生的worker的一些方法和屬性。我們將在下一節中看到一些示例。集群模塊包含多個事件。與工作進程啟動和終止時刻相關的兩個常見事件是online和exit事件。當工作進程派生並發送online消息時,會發出online事件。當工作進程死亡時,會發出exit事件。稍後,我們將了解如何使用這兩個事件來控制工作進程的生命週期。現在,讓我們將到目前為止看到的所有內容放在一起,並展示一個完整的可運行示例。
示例
本節包含兩個示例。第一個示例是一個簡單的應用程序,顯示如何在Node.js應用程序中使用集群模塊。第二個示例是一個利用Node.js集群模塊的Express服務器,它是我通常在大規模項目中使用的生產代碼的一部分。這兩個示例都可以從GitHub下載。
在這個第一個示例中,我們設置了一個簡單的服務器,它使用包含處理請求的工作進程ID的消息來響應所有傳入請求。主進程派生四個工作進程。在每個工作進程中,我們開始監聽端口8000以接收傳入請求。實現我剛才描述的內容的代碼如下所示:
var cluster = require('cluster'); var http = require('http'); var numCPUs = 4; if (cluster.isMaster) { for (var i = 0; i < numCPUs; i++) { cluster.fork(); } } else { http.createServer(function(req, res) { res.writeHead(200); res.end('process ' + process.pid + ' says hello!'); }).listen(8000); }
您可以通過啟動服務器(運行命令node simple.js)並訪問URL https://www.php.cn/link/7d2d180c45c41870f36e747816456190。
Express是Node.js最流行的Web應用程序框架之一(如果不是最流行的話)。在本網站上,我們已經多次介紹過它。如果您有興趣了解更多信息,我建議您閱讀文章《使用Express 4創建RESTful API》和《構建Node.js驅動的聊天室Web應用程序:Express和Azure》。第二個示例顯示瞭如何開發高度可擴展的Express服務器。它還演示瞭如何遷移單個進程服務器以利用少量代碼的集群模塊。
var cluster = require('cluster');
此示例的第一個補充是使用Node.js os模塊獲取CPU內核的數量。 os模塊包含一個cpus()函數,它返回一個CPU內核數組。使用這種方法,我們可以根據服務器規格動態確定要派生的工作進程數量,以最大限度地利用資源。第二個更重要的補充是處理工作進程的死亡。當工作進程死亡時,集群模塊會發出exit事件。可以通過偵聽該事件並在發出該事件時執行回調函數來處理它。您可以通過編寫類似cluster.on('exit', callback);的語句來做到這一點。在回調函數中,我們派生一個新的工作進程以保持預期的工作進程數量。這允許我們保持應用程序運行,即使有一些未處理的異常。在這個示例中,我還為online事件設置了一個偵聽器,每當派生工作進程並準備好接收傳入請求時,就會發出該事件。這可用於日誌記錄或其他操作。
有幾種工具可以對API進行基準測試,但這裡我使用Apache Benchmark工具來分析使用集群模塊如何影響應用程序的性能。為了設置測試,我開發了一個Express服務器,它有一個路由和一個用於該路由的回調函數。在回調函數中,執行一個虛擬操作,然後返回一條簡短的消息。服務器有兩個版本:一個沒有工作進程,其中所有操作都在主進程中發生,另一個有8個工作進程(因為我的機器有8個內核)。下表顯示了合併集群模塊如何增加每秒處理的請求數。
并发连接 | 1 | 2 | 4 | 8 | 16 |
---|---|---|---|---|---|
单进程 | 654 | 711 | 783 | 776 | 754 |
8个工作进程 | 594 | 1198 | 2110 | 3010 | 3024 |
(每秒處理的請求數)
高級操作
雖然使用集群模塊相對簡單,但您可以使用工作進程執行其他操作。例如,您可以使用集群模塊實現應用程序的(幾乎!)零停機時間。我們過一會兒將了解如何執行其中一些操作。
有時,您可能需要從主進程向工作進程發送消息以分配任務或執行其他操作。作為回報,工作進程可能需要通知主進程任務已完成。要偵聽消息,應在主進程和工作進程中都設置message事件的事件偵聽器:
var cluster = require('cluster');
worker對像是fork()方法返回的引用。要在工作進程中偵聽來自主進程的消息:
if(cluster.isMaster) { ... }
消息可以是字符串或JSON對象。要向特定工作進程發送消息,您可以編寫如下所示的代碼:
cluster.fork();
類似地,要向主進程發送消息,您可以編寫:
var cluster = require('cluster'); var http = require('http'); var numCPUs = 4; if (cluster.isMaster) { for (var i = 0; i < numCPUs; i++) { cluster.fork(); } } else { http.createServer(function(req, res) { res.writeHead(200); res.end('process ' + process.pid + ' says hello!'); }).listen(8000); }
在Node.js中,消息是通用的,沒有特定類型。因此,最好將消息作為包含有關消息類型、發送者和內容本身的一些信息的JSON對象發送。例如:
var cluster = require('cluster'); if(cluster.isMaster) { var numWorkers = require('os').cpus().length; console.log('Master cluster setting up ' + numWorkers + ' workers...'); for(var i = 0; i < numWorkers; i++) { cluster.fork(); } cluster.on('online', function(worker) { console.log('Worker ' + worker.process.pid + ' is online'); }); cluster.on('exit', function(worker, code, signal) { console.log('Worker ' + worker.process.pid + ' died with code: ' + code + ', and signal: ' + signal); console.log('Starting a new worker'); cluster.fork(); }); } else { var app = require('express')(); app.all('/*', function(req, res) {res.send('process ' + process.pid + ' says hello!').end();}) var server = app.listen(8000, function() { console.log('Process ' + process.pid + ' is listening to all incoming requests'); }); }
這裡需要注意的一點是,message事件回調是異步處理的。沒有定義的執行順序。您可以在GitHub上找到主進程和工作進程之間通信的完整示例。
使用工作進程可以實現的一個重要結果是(幾乎)零停機時間服務器。在主進程中,您可以在對應用程序進行更改後,一次一個地終止和重新啟動工作進程。這允許您在加載新版本的同時運行舊版本。為了能夠在運行時重新啟動應用程序,您需要記住兩點。首先,主進程一直運行,只有工作進程被終止和重新啟動。因此,重要的是要使主進程保持簡短,並且只負責管理工作進程。其次,您需要以某種方式通知主進程需要重新啟動工作進程。有幾種方法可以做到這一點,包括用戶輸入或監視文件更改。後者效率更高,但您需要在主進程中識別要監視的文件。我建議重新啟動工作進程的方法是首先嘗試安全地關閉它們;然後,如果它們沒有安全終止,則強制殺死它們。您可以通過向工作進程發送關閉消息來執行前者,如下所示:
worker.on('message', function(message) { console.log(message); });
並在工作進程message事件處理程序中啟動安全關閉:
process.on('message', function(message) { console.log(message); });
要對所有工作進程執行此操作,您可以使用集群模塊的workers屬性,該屬性保存對所有正在運行的工作進程的引用。我們還可以將所有任務包裝在主進程中的一個函數中,該函數可以在我們想要重新啟動所有工作進程時調用。
var cluster = require('cluster');
我們可以從集群模塊中的workers對象獲取所有正在運行的工作進程的ID。此對象保存對所有正在運行的工作進程的引用,並在終止和重新啟動工作進程時動態更新。首先,我們將所有正在運行的工作進程的ID存儲在workerIds數組中。這樣,我們避免重新啟動新派生的工作進程。然後,我們請求每個工作進程安全關閉。如果5秒後工作進程仍在運行並且仍然存在於workers對像中,那麼我們調用工作進程上的kill函數以強制其關閉。您可以在GitHub上找到一個實際示例。
結論
可以使用集群模塊對Node.js應用程序進行並行化,以便更有效地使用系統。可以使用幾行代碼同時運行多個進程,這使得遷移相對容易,因為Node.js處理困難的部分。正如我在性能比較中所展示的那樣,通過更有效地利用系統資源,應用程序性能有可能得到顯著提高。除了性能之外,您還可以通過在應用程序運行時重新啟動工作進程來提高應用程序的可靠性和正常運行時間。也就是說,在考慮在應用程序中使用集群模塊時,您需要注意。集群模塊的主要推薦用途是用於Web服務器。在其他情況下,您需要仔細研究如何在工作進程之間分配任務,以及如何在工作進程和主進程之間有效地溝通進度。即使對於Web服務器,在對應用程序進行任何更改之前,也要確保單個Node.js進程是瓶頸(內存或CPU),因為您的更改可能會引入錯誤。最後一點,Node.js網站為集群模塊提供了很好的文檔。因此,請務必查看一下!
關於Node.js集群的常見問題解答(FAQ)
使用Node.js集群的主要優勢是提高應用程序的性能。 Node.js在一個線程上運行,這意味著它一次只能利用一個CPU內核。但是,現代服務器通常有多個內核。通過使用Node.js集群,您可以創建一個主進程,該進程派生多個工作進程,每個工作進程都在不同的CPU內核上運行。這允許您的應用程序同時處理更多請求,從而顯著提高其速度和性能。
Node.js集群通過創建一個主進程來工作,該主進程派生多個工作進程。主進程偵聽傳入請求並將它們以輪詢方式分發給工作進程。每個工作進程都在單獨的CPU內核上運行並獨立處理請求。這允許您的應用程序利用所有可用的CPU內核並同時處理更多請求。
創建Node.js集群涉及使用Node.js提供的“cluster”模塊。首先,您需要導入“cluster”和“os”模塊。然後,您可以使用“cluster.fork()”方法來創建工作進程。 “os.cpus().length”為您提供可用的CPU內核數,您可以使用它來確定要創建的工作進程數。這是一個簡單的示例:
var cluster = require('cluster');
您可以通過偵聽主進程上的“exit”事件來處理Node.js集群中的工作進程崩潰。當工作進程崩潰時,它會向主進程發送“exit”事件。然後,您可以使用“cluster.fork()”方法來創建一個新的工作進程以替換崩潰的工作進程。這是一個示例:
if(cluster.isMaster) { ... }
是的,您可以將Node.js集群與Express.js一起使用。事實上,使用Node.js集群可以顯著提高Express.js應用程序的性能。您只需要將Express.js應用程序代碼放在集群腳本中的工作進程代碼塊中即可。
雖然Node.js集群可以顯著提高應用程序的性能,但它也有一些局限性。例如,工作進程不共享狀態或內存。這意味著您不能將會話數據存儲在內存中,因為它在所有工作進程中都不可訪問。相反,您需要使用共享會話存儲,例如數據庫或Redis服務器。
默認情況下,Node.js集群中的主進程以輪詢方式將傳入請求分發給工作進程。這提供了一種基本的負載均衡形式。但是,如果您需要更高級的負載均衡,則可能需要使用反向代理服務器,例如Nginx。
是的,您可以在生產環境中使用Node.js集群。事實上,強烈建議在生產環境中使用Node.js集群,以充分利用服務器的CPU內核並提高應用程序的性能。
調試Node.js集群可能有點棘手,因為您有多個工作進程同時運行。但是,您可以對每個工作進程使用具有唯一端口的“inspect”標誌來將調試器附加到每個進程。這是一個示例:
cluster.fork();
是的,您可以將Node.js集群與其他Node.js模塊一起使用。但是,您需要注意的是,工作進程不共享狀態或內存。這意味著如果模塊依賴於共享狀態,它可能無法在集群環境中正常工作。
以上是如何創建一個node.js群集以加速您的應用的詳細內容。更多資訊請關注PHP中文網其他相關文章!