JavaScript >> Javascript 文檔 >  >> Node.js

設置 Node.js 集群

我們都知道 Node.js 擅長異步處理大量事件,但是很多人 知道的是,所有這些都是在一個線程上完成的。 Node.js 實際上並不是多線程的,所以所有這些請求都只是在單線程的事件循環中處理。

那麼,為什麼不通過使用 Node.js 集群來充分利用您的四核處理器呢?這將啟動您的代碼的多個實例以處理更多請求。這聽起來可能有點困難,但實際上使用 Node.js v0.8 中引入的集群模塊非常容易。

顯然,這對於任何可以在不同進程之間分配工作的應用程序都有幫助,但對於處理大量 IO 請求的應用程序(例如網站)尤其重要。

不幸的是,由於並行處理的複雜性,在服務器上集群應用程序並不總是直截了當的。當你需要多個進程監聽同一個端口時,你會怎麼做?回想一下,在任何給定時間只有一個進程可以訪問端口。這裡最幼稚的解決方案是配置每個進程監聽不同的端口,然後設置 Nginx 對端口之間的請求進行負載均衡。

這是一個可行的解決方案,但它需要更多的工作來設置和配置每個進程,更不用說配置 Nginx。使用此解決方案,您只需添加更多內容供您自己管理。

相反,您可以將主進程分叉為多個子進程(通常每個處理器有一個子進程)。在這種情況下,孩子 允許與父級共享一個端口(感謝進程間通信或IPC),因此無需擔心管理多個端口。

這正是 cluster 模塊為您服務。

使用集群模塊

對應用程序進行集群非常簡單,尤其是對於 Express 項目等 Web 服務器代碼。你真正需要做的就是:

var cluster = require('cluster');
var express = require('express');
var numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
    for (var i = 0; i < numCPUs; i++) {
        // Create a worker
        cluster.fork();
    }
} else {
    // Workers share the TCP connection in this server
    var app = express();

    app.get('/', function (req, res) {
        res.send('Hello World!');
    });

    // All workers use this port
    app.listen(8080);
}

代碼的功能分為兩部分,主代碼和工作代碼。這是在 if 語句中完成的 (if (cluster.isMaster) {...} )。這裡master的唯一目的是創建所有的worker(創建的worker數量基於可用的CPU數量),worker負責運行Express服務器的獨立實例。

當一個工作者從主進程中分叉出來時,它會從模塊的開頭重新運行代碼。當工作人員到達 if 語句時,它返回 false 對於 cluster.isMaster ,因此它將創建 Express 應用程序、路由,然後偵聽端口 8080 .在四核處理器的情況下,我們會產生四個工作人員,所有工作人員都在同一個端口上監聽請求。

但是如何在工作人員之間分配請求?顯然,他們不能(也不應該)都在傾聽和響應我們收到的每一個請求。為了解決這個問題,cluster 中實際上有一個嵌入式負載均衡器 處理在不同工作人員之間分配請求的模塊。在 Linux 和 OSX(但不是 Windows)上,循環 (cluster.SCHED_RR ) 策略默認生效。唯一可用的其他調度選項是將其留給操作系統(cluster.SCHED_NONE ),這是 Windows 上的默認設置。

調度策略可以在 cluster.schedulingPolicy 中設置 或者通過在環境變量 NODE_CLUSTER_SCHED_POLICY 上設置它 (值為 'rr' 或 'none')。

您可能還想知道不同的進程如何共享一個端口。運行這麼多進程來處理網絡請求的困難之處在於,傳統上只有一個進程可以同時打開一個端口。 cluster 的最大好處 是它為您處理端口共享,因此所有孩子都可以訪問您打開的任何端口,例如網絡服務器。這是通過 IPC 完成的,這意味著 master 只需將端口句柄發送給每個 worker。

多虧了這樣的功能,集群變得超級簡單。

cluster.fork() 與 child_process.fork()

如果您之前有使用 child_process 的經驗 的fork() 方法,那麼您可能會認為 cluster.fork() 有點相似(它們在很多方面都是相似的),因此我們將在本節中解釋這兩種分叉方法的一些關鍵區別。

cluster.fork() 之間有幾個主要區別 和 child_process.fork() . child_process.fork() 方法有點低級,需要您將模塊的位置(文件路徑)作為參數傳遞,以及其他可選參數,如當前工作目錄、擁有進程的用戶、環境變量等。

另一個區別是 cluster 從它運行的同一模塊的開頭開始執行工作程序。因此,如果您的應用的入口點是 index.js ,但工人是在 cluster-my-app.js ,那麼它仍然會從 index.js 處開始執行 . child_process 不同之處在於它在傳遞給它的任何文件中生成執行,而不一定是給定應用程序的入口點。

您可能已經猜到 cluster 模塊實際上使用 child_process 下面的模塊用於創建孩子,這是用 child_process 自己的fork() 方法,允許他們通過 IPC 進行通信,這就是工人之間共享端口句柄的方式。

需要明確的是,Node 中的 fork 與 POISIX 的 fork 非常不同,因為它實際上並不克隆當前進程,但它確實啟動了一個新的 V8 實例。

儘管這是最簡單的多線程方法之一,但應謹慎使用。僅僅因為你能夠產生 1,000 名工人並不意味著你應該這樣做。每個worker都佔用系統資源,所以只產生那些真正需要的。 Node 文檔指出,由於每個子進程都是一個新的 V8 實例,因此您需要預計每個子進程的啟動時間為 30ms,每個實例至少需要 10mb 的內存。

錯誤處理

那麼,當您的一名(或多名!)工人死亡時,您會怎麼做?如果您無法在工作人員崩潰後重新啟動它們,那麼集群的全部意義就基本喪失了。 cluster 祝你好運 模塊擴展 EventEmitter 並提供一個“退出”事件,它會告訴您您的一個工人孩子何時死亡。

您可以使用它來記錄事件並重新啟動該過程:

免費電子書:Git Essentials

查看我們的 Git 學習實踐指南,其中包含最佳實踐、行業認可的標準以及隨附的備忘單。停止谷歌搜索 Git 命令並真正學習 它!

cluster.on('exit', function(worker, code, signal) {
    console.log('Worker %d died with code/signal %s. Restarting worker...', worker.process.pid, signal || code);
    cluster.fork();
});

現在,只需 4 行代碼,您就擁有了自己的內部流程管理器!

性能比較

好的,現在到有趣的部分。讓我們看看聚類實際上對我們有多大幫助。

對於這個實驗,我設置了一個類似於我上面顯示的示例代碼的網絡應用程序。但最大的不同是我們通過使用 sleep 模塊並通過向用戶返回一堆隨機數據來模擬 Express 路由中正在完成的工作。

這是同一個網絡應用,但帶有集群:

var cluster = require('cluster');
var crypto = require('crypto');
var express = require('express');
var sleep = require('sleep');
var numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
    for (var i = 0; i < numCPUs; i++) {
        // Create a worker
        cluster.fork();
    }
} else {
    // Workers share the TCP connection in this server
    var app = express();

    app.get('/', function (req, res) {
        // Simulate route processing delay
        var randSleep = Math.round(10000 + (Math.random() * 10000));
        sleep.usleep(randSleep);

        var numChars = Math.round(5000 + (Math.random() * 5000));
        var randChars = crypto.randomBytes(numChars).toString('hex');
        res.send(randChars);
    });

    // All workers use this port
    app.listen(8080);
}

這是我們將進行比較的“控制”代碼。它本質上是完全相同的東西,只是沒有 cluster.fork()

var crypto = require('crypto');
var express = require('express');
var sleep = require('sleep');

var app = express();

app.get('/', function (req, res) {
    // Simulate route processing delay
    var randSleep = Math.round(10000 + (Math.random() * 10000));
    sleep.usleep(randSleep);

    var numChars = Math.round(5000 + (Math.random() * 5000));
    var randChars = crypto.randomBytes(numChars).toString('hex');
    res.send(randChars);
});

app.listen(8080);

為了模擬繁重的用戶負載,我們將使用一個名為 Siege 的命令行工具,我們可以使用它向我們選擇的 URL 發出一堆同時請求。

Siege 也很不錯,因為它可以跟踪性能指標,例如可用性、吞吐量和處理請求的速率。

這是我們將用於測試的 Siege 命令:

$ siege -c100 -t60s http://localhost:8080/

對兩個版本的應用程序運行此命令後,以下是一些更有趣的結果:

類型 處理的請求總數 請求/秒 平均響應時間 吞吐量
無聚類 3467 58.69 1.18 秒 0.84 MB/秒
集群(4個進程) 11146 188.72 0.03 秒 2.70 MB/秒

如您所見,集群應用程序在列出的幾乎所有指標上都比單進程應用程序改進了大約 3.2 倍,但平均響應時間除外,後者的改進更為顯著。


Tutorial JavaScript 教程
  1. LeetCode - 最大乘積子數組

  2. 如何使用 ESLint 和 Prettier 提高代碼質量?

  3. 我作為一名自學成才的開發人員從零到第一份開發人員工作的旅程。

  4. 使用 Vite 創建一個新的 React 應用

  5. 使用 WebRTC 進行簡單的視頻聊天

  6. 開放日誌[1]

  7. 使用 CSS 播放按鈕覆蓋圖像

  1. React Router Dom v6 - 更改和更新

  2. Javascript:兒童遊戲

  3. 使用 CSS 和 Javascript 將滑塊添加到您的網站

  4. 使用 Kendo UI for Angular 的新金融投資組合演示

  5. 使用 JQuery 禁用和啟用所有超鏈接

  6. 遍歷 JavaScript 數組並動態查找深層嵌套值

  7. 使用 Rxjs 破解 Angular 表單🔥

  1. 全棧開發者係列 - 在 2021 年及以後學習編碼

  2. 在 Microsoft App Center 上簽署 React Native Android APK

  3. 如何使用 CSS 和 JavaScript 製作可變主題

  4. 讓,異步,等待作為變量