Node.js集群技术完全指南:提升应用性能与扩展性的10种方法

引言

当您在具有多个CPU的系统上运行Node.js程序时,默认情况下它会创建一个仅使用单个CPU执行进程。由于Node.js使用单线程来执行JavaScript代码,所有对应用程序的请求都必须由该线程在单个CPU上处理。如果应用程序有CPU密集型任务,操作系统必须调度它们共享单个CPU直到完成。如果进程接收到过多请求,可能会导致单个进程过载,从而降低性能。如果进程崩溃,用户将无法访问您的应用程序。

作为解决方案,Node.js引入了集群模块,它在同一台机器上创建多个相同应用程序的副本,并让它们同时运行。它还附带一个负载均衡器,使用轮询算法在进程之间均匀分配负载。如果单个实例崩溃,用户仍可以由仍在运行的剩余进程提供服务。由于负载在多个进程之间均匀共享,防止了单个实例过载,应用程序的性能显著提高。

在本教程中,您将在具有四个或更多CPU的机器上使用集群模块扩展Node.js应用程序。您将创建一个不使用集群的应用程序,然后修改应用程序以使用集群。您还将使用pm2模块在多个CPU上扩展应用程序。您将使用负载测试工具比较使用集群和不使用集群的应用程序的性能,并评估pm2模块。

先决条件

要学习本教程,您需要以下条件:

  • 一个具有四个或更多CPU的系统。如果您使用的是带有Ubuntu 22.04的远程服务器,可以按照我们的《初始服务器设置》指南设置系统。
  • 在您的开发环境中设置Node.js。如果您运行的是Ubuntu 22.04,请遵循《如何在Ubuntu 22.04上安装Node.js》的选项3。对于其他系统,请访问《如何安装Node.js并创建本地开发环境》。
  • 使用Express的基本知识,您可以通过《Node.js和Express入门指南》来复习。

步骤1 – 设置项目目录

在此步骤中,您将为项目创建目录并下载将在本教程后面构建的应用程序的依赖项。在步骤2中,您将使用Express构建应用程序。然后,您将在步骤3中使用内置的node-cluster模块将其扩展到多个CPU,并将在步骤4中使用loadtest包进行测量。之后,您将使用pm2包进行扩展并在步骤5中再次测量。

首先,创建一个目录。您可以将其命名为cluster_demo或您喜欢的任何目录名称:

  1. mkdir cluster_demo

接下来,进入该目录:

  1. cd cluster_demo

然后,初始化项目,这将同时创建一个package.json文件:

  1. npm init -y

-y选项告诉NPM接受所有默认选项。

当命令运行时,它将产生与以下匹配的输出:

输出写入 /home/sammy/cluster_demo/package.json: { "name": "cluster_demo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }

注意这些与您的特定项目对齐的属性:

  • name:npm包的名称。
  • version:您的包的版本号。
  • main:您项目的入口点。

要了解其他属性的更多信息,您可以查看NPM文档中的package.json部分。

接下来,使用您喜欢的编辑器打开package.json文件(本教程将使用nano):

  1. nano package.json

在您的package.json文件中,添加高亮文本以启用对ES模块导入的支持:

cluster_demo/package.json

{
  ...
  "author": "",
  "license": "ISC",
  "type": "module"
}

使用CTRL+X保存并关闭文件。

接下来,您将下载以下软件包:

  • express:一个用于在Node.js中构建Web应用程序的框架。
  • loadtest:一个负载测试工具,用于生成流量以测量应用程序的性能。
  • pm2:一个将应用程序自动扩展到多个CPU的工具。

运行以下命令下载Express软件包:

  1. npm install express

接下来,运行命令全局下载loadtest和pm2软件包:

  1. npm install -g loadtest pm2

既然您已经安装了必要的依赖项,您将创建一个不使用集群的应用程序。

步骤2 — 创建不使用集群的应用程序

在此步骤中,您将创建一个包含单个路由的示例程序,该程序将在每次用户访问时启动CPU密集型任务。该程序不会使用集群模块,以便您可以了解在单个CPU上运行单个应用程序实例的性能影响。稍后在本教程中,您将这种方法与集群模块的性能进行比较。

使用nano或您喜欢的文本编辑器,创建index.js文件:

  1. nano index.js

在您的index.js文件中,添加以下行以导入并实例化Express:

cluster_demo/index.js

import express from "express";

const port = 3000;
const app = express();

console.log(`worker pid=${process.pid}`);

在第一行中,您导入了express软件包。在第二行中,您将port变量设置为端口3000,应用程序的服务器将在此端口上监听。接下来,您将app变量设置为Express的实例。之后,您使用内置的process模块在控制台中记录应用程序进程的进程ID。

接下来,添加这些行来定义/heavy路由,该路由将包含一个CPU密集型循环:

cluster_demo/index.js

...
app.get("/heavy", (req, res) => {
  let total = 0;
  for (let i = 0; i < 5_000_000; i++) {
    total++;
  }
  res.send(`CPU密集型任务的结果是 ${total}\n`);
});

在 /heavy 路由中,您定义了一个循环,将 total 变量递增500万次。然后,您使用 res.send() 方法发送一个包含 total 变量值的响应。虽然这个CPU密集型任务的示例是任意的,但它展示了CPU密集型任务而不会增加复杂性。您也可以为路由使用其他名称,但本教程使用 /heavy 来表示一个性能繁重的任务。

接下来,调用Express模块的listen()方法,使服务器监听存储在port变量中的端口3000:

cluster_demo/index.js
...
app.listen(port, () => {
  console.log(`应用程序正在监听端口 ${port}`);
});

完整文件将如下所示:

cluster_demo/index.js
import express from "express";

const port = 3000;
const app = express();

console.log(`worker pid=${process.pid}`);

app.get("/heavy", (req, res) => {
  let total = 0;
  for (let i = 0; i < 5_000_000; i++) {
    total++;
  }
  res.send(`The result of the CPU intensive task is ${total}\n`);
});

app.listen(port, () => {
  console.log(`App listening on port ${port}`);
});

完成代码添加后,保存并退出文件。然后使用node命令运行该文件:

  1. node index.js

运行该命令后,输出将如下所示:

输出worker pid=11023 App listening on port 3000

输出了正在运行的进程ID以及确认服务器正在监听端口3000的消息。

要测试应用程序是否正常工作,请打开另一个终端并运行以下命令:

  1. curl http://localhost:3000/heavy

注意:

如果您在远程服务器上按照本教程操作,请打开另一个终端,然后输入以下命令:

ssh -L 3000:localhost:3000 your_non_root_user@your_server_ip

连接后,输入以下命令使用curl向应用发送请求:

  1. curl http://localhost:3000/heavy

输出将匹配以下内容:

输出 The result of the CPU intensive task is 5000000

输出来自CPU密集型计算的结果。

此时,您可以使用CTRL+C停止服务器。

当您使用node命令运行index.js文件时,操作系统(OS)会创建一个进程。进程是操作系统为正在运行的程序创建的抽象。操作系统为程序分配内存,并在包含所有操作系统进程的进程列表中创建一个条目。该条目就是一个进程ID。

然后,程序二进制文件被定位并加载到分配给进程的内存中。从那里开始执行。在运行过程中,它不知道系统中的其他进程,进程中发生的任何事情都不会影响其他进程。

由于您的Node.js应用程序在具有多个CPU的服务器上只运行一个进程,它将接收和处理所有传入的请求。在此图中,所有传入的请求都定向到在单个CPU上运行的进程,而其他CPU保持空闲状态:

显示一个进程在具有四个CPU的机器的一个CPU上运行,处理所有传入请求的图表

现在您已经创建了一个不使用集群模块的应用程序,接下来您将使用集群模块来扩展应用程序以使用多个CPU。

步骤 3 — 集群化应用程序

在这一步中,您将添加集群模块以创建同一程序的多个实例,以处理更多负载并提高性能。当您使用集群模块运行进程时,您可以在机器的每个CPU上运行多个进程:

具有四个CPU的机器的每个CPU上运行的进程共享负载的屏幕截图

在此图中,请求通过主进程中的负载均衡器,然后使用轮询算法在进程之间分配请求。

现在您将添加集群模块。在您的终端中,创建primary.js文件:

  1. nano primary.js

在您的primary.js文件中,添加以下行来导入依赖项:

cluster_demo/primary.js

import cluster from "cluster";
import os from "os";
import { dirname } from "path";
import { fileURLToPath } from "url";

const __dirname = dirname(fileURLToPath(import.meta.url));

在前两行中,您导入了cluster和os模块。在接下来的两行中,您导入了dirname和fileURLToPath,它们用于将__dirname变量值设置为执行index.js文件的目录的绝对路径。这些导入是必要的,因为在使用ES模块时未定义__dirname,它仅在CommonJS模块中默认定义。

接下来,添加以下代码来引用index.js文件:

cluster_demo/primary.js
...
const cpuCount = os.cpus().length;

console.log(`The total number of CPUs is ${cpuCount}`);
console.log(`Primary pid=${process.pid}`);
cluster.setupPrimary({
  exec: __dirname + "/index.js",
});

首先,您将cpuCount变量设置为机器中的CPU数量,该数量应该为四个或更高。接下来,您在控制台中记录CPU的数量。然后,您记录主进程的进程ID,该进程将接收所有请求,并使用负载均衡器将它们分配给工作进程。

之后,您使用cluster模块的setupPrimary()方法引用index.js文件,以便在每个生成的工作进程中执行它。

接下来,添加以下代码来创建进程:

cluster_demo/primary.js
...
for (let i = 0; i < cpuCount; i++) {
  cluster.fork();
}
cluster.on("exit", (worker, code, signal) => {
  console.log(`工作进程 ${worker.process.pid} 已被终止`);
  console.log("正在启动新的工作进程");
  cluster.fork();
});

该循环会迭代cpuCount值的次数,并在每次迭代中调用cluster模块的fork()方法。您使用cluster模块的on()方法附加exit事件,以监听进程何时发出exit事件,这通常发生在进程终止时。当exit事件被触发时,您会记录已终止的工作进程的进程ID,然后调用fork()方法创建一个新的工作进程来替换已终止的进程。

您的完整代码现在将与以下内容匹配:

cluster_demo/primary.js
import cluster from "cluster";
import os from "os";
import { dirname } from "path";
import { fileURLToPath } from "url";

const __dirname = dirname(fileURLToPath(import.meta.url));

const cpuCount = os.cpus().length;

console.log(`CPU总数为 ${cpuCount}`);
console.log(`主进程pid=${process.pid}`);
cluster.setupPrimary({
  exec: __dirname + "/index.js",
});

for (let i = 0; i < cpuCount; i++) {
  cluster.fork();
}
cluster.on("exit", (worker, code, signal) => {
  console.log(`工作进程 ${worker.process.pid} 已被终止`);
  console.log("正在启动新的工作进程");
  cluster.fork();
});

完成添加这些代码行后,保存并退出文件。

Next, run the file:

  1. node primary.js

The output will closely match the following (your process IDs and order of information may differ):

OutputThe total number of CPUs is 4 Primary pid=7341 worker pid=7353 worker pid=7354 worker pid=7360 App listening on port 3000 App listening on port 3000 App listening on port 3000 worker pid=7352 App listening on port 3000

The output will indicate four CPUs, one primary process that includes a load balancer, and four worker processes listening on port 3000.

Next, return to the second terminal, then send a request to the /heavy route:

  1. curl http://localhost:3000/heavy

The output confirms the program is working:

OutputThe result of the CPU intensive task is 5000000

You can stop the server now.

At this point, you will have four processes running on all the CPUs on your machine:

Screenshot of processes running on each CPU of a machine that has four CPUs sharing load

With clustering added to the application, you can compare the program performances for the one using the cluster module and the one without the cluster module.

Step 4 — Comparing Performance Using a Load Testing Tool

In this step, you will use the loadtest package to generate traffic against the two programs you’ve built. You’ll compare the performance of the primary.js program which uses the cluster module with that of the index.js program which does not use clustering. You will notice that the program using the cluster module performs faster and can handle more requests within a specific time than the program that doesn’t use clustering.

First, you will measure the performance of the index.js file, which doesn’t use the cluster module and only runs on a single instance.

In your first terminal, run the index.js file to start the server:

  1. node index.js

You’ll receive an output that the app is running:

Outputworker pid=7731 App listening on port 3000

Next, return to your second terminal to use the loadtest package to send requests to the server:

  1. loadtest -n 1200 -c 200 -k http://localhost:3000/heavy

The -n option accepts the number of requests the package should send, which is 1200 requests here. The -c option accepts the number of requests that should be sent simultaneously to the server.

Once the requests have been sent, the package will return output similar to the following:

OutputRequests: 0 (0%), requests per second: 0, mean latency: 0 ms Requests: 430 (36%), requests per second: 87, mean latency: 1815.1 ms Requests: 879 (73%), requests per second: 90, mean latency: 2230.5 ms Target URL: http://localhost:3000/heavy Max requests: 1200 Concurrency level: 200 Agent: keepalive Completed requests: 1200 Total errors: 0 Total time: 13.712728601 s Requests per second: 88 Mean latency: 2085.1 ms Percentage of the requests served within a certain time 50% 2234 ms 90% 2340 ms 95% 2385 ms 99% 2406 ms 100% 2413 ms (longest request)

From this output, take note of the following metrics:

  • Total time measures how long it took for all the requests to be served. In this output, it took just over 13 seconds to serve all 1200 requests.
  • Requests per second measures the number of requests the server can handle per second. In this output, the server handles 88 requests per second.
  • Mean latency measures the time it took to send a request and get a response, which is 2085.1 ms in the sample output.

These metrics will vary depending on your network or processor speed, but they will be close to these examples.

Now that you have measured the performance of the index.js file, you can stop the server.

Next, you will measure the performance of the primary.js file, which uses the cluster module.

为此,请返回第一个终端并重新运行primary.js文件:

  1. node primary.js

您将收到与之前相同的信息的响应:

输出CPU总数为4 主进程 pid=7841 工作进程 pid=7852 应用程序正在监听端口3000 工作进程 pid=7854 应用程序正在监听端口3000 工作进程 pid=7853 工作进程 pid=7860 应用程序正在监听端口3000 应用程序正在监听端口3000

在第二个终端中,再次运行loadtest命令:

  1. loadtest -n 1200 -c 200 -k http://localhost:3000/heavy

当测试完成时,您将收到类似的输出(可能会根据您系统上的CPU数量而有所不同):

输出请求:0 (0%),每秒请求数:0,平均延迟:0 毫秒 目标URL: http://localhost:3000/heavy 最大请求数: 1200 并发级别: 200 代理: keepalive 已完成请求: 1200 总错误数: 0 总时间: 3.412741962 秒 每秒请求数: 352 平均延迟: 514.2 毫秒 在特定时间内服务的请求百分比 50% 194 毫秒 90% 2521 毫秒 95% 2699 毫秒 99% 2710 毫秒 100% 2759 毫秒(最长请求)

使用集群模块运行的primary.js应用程序的输出表明,总时间从不使用集群技术的程序中的13秒减少到3秒。服务器每秒可以处理的请求数量从之前的88个增加到352个,增加了两倍,这意味着您的服务器可以承受巨大的负载。另一个重要指标是平均延迟,已从2085.1毫秒显著下降到514.2毫秒。

此响应确认扩展已经成功,您的应用程序可以在短时间内处理更多请求而不会出现延迟。如果您升级机器以拥有更多CPU,应用程序将自动扩展到CPU数量并进一步提高性能。

提醒一下,由于您的网络和处理器速度不同,终端输出中的指标会有所不同。总时间和平均延迟将显著下降,而总时间将迅速增加。

既然您已经进行了比较并注意到应用程序在使用集群模块时表现更好,您可以停止服务器。在下一步中,您将使用pm2替代集群模块。

第5步 — 使用pm2进行集群化

到目前为止,您已经使用集群模块根据机器上的CPU数量创建工作进程。您还添加了在工作进程崩溃时重新启动它的功能。在这一步中,您将设置一个替代方案,通过使用基于集群模块构建的pm2进程管理器来自动扩展应用程序。这个进程管理器包含一个负载均衡器,可以自动创建与机器上CPU数量相同的工作进程。它还允许您监控进程,并在某个进程崩溃时自动生成一个新的工作进程。

要使用它,您需要使用需要扩展的文件运行pm2包,在本教程中是index.js文件。

在您的初始终端中,使用以下命令启动pm2集群:

  1. pm2 start index.js -i 0

-i选项接受您希望pm2创建的工作进程数量。如果传递参数0,pm2将自动创建与机器上CPU数量相同的工作进程。

运行该命令后,pm2将向您显示有关工作进程的更多详细信息:

输出


[PM2] Spawning PM2 daemon with pm2_home=/home/sammy/.pm2
[PM2] PM2 Successfully daemonized
[PM2] Starting /home/sammy/cluster_demo/index.js in cluster_mode (0 instance)
[PM2] Done.
┌─────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
├─────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0 │ index │ default │ 1.0.0 │ cluster │ 7932 │ 0s │ 0 │ online │ 0% │ 54.5mb │ nod… │ disabled │
│ 1 │ index │ default │ 1.0.0 │ cluster │ 7939 │ 0s │ 0 │ online │ 0% │ 50.9mb │ nod… │ disabled │
│ 2 │ index │ default │ 1.0.0 │ cluster │ 7946 │ 0s │ 0 │ online │ 0% │ 51.3mb │ nod… │ disabled │
│ 3 │ index │ default │ 1.0.0 │ cluster │ 7953 │ 0s │ 0 │ online │ 0% │ 47.4mb │ nod… │ disabled │
└─────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

The table contains each worker’s process ID, status, CPU utilization, and memory consumption, which you can use to understand how the processes behave.

When starting the cluster with pm2, the package runs in the background and will automatically restart even when you reboot your system.

If you want to read the logs from the worker processes, you can use the following command:

  1. pm2 logs

You’ll receive an output of the logs:

Output[TAILING] Tailing last 15 lines for [all] processes (change the value with --lines option) /home/sammy/.pm2/pm2.log last 15 lines: ... PM2 | 2022-12-25T17:48:37: PM2 log: App [index:3] starting in -cluster mode- PM2 | 2022-12-25T17:48:37: PM2 log: App [index:3] online /home/sammy/.pm2/logs/index-error.log last 15 lines: /home/sammy/.pm2/logs/index-out.log last 15 lines: 0|index | worker pid=7932 0|index | App listening on port 3000 0|index | worker pid=7953 0|index | App listening on port 3000 0|index | worker pid=7946 0|index | worker pid=7939 0|index | App listening on port 3000 0|index | App listening on port 3000

In the last eight lines, the log provides the output from each of the four running worker processes, including the process ID and the port number 3000. This output confirms that all the processes are running.

You can also check the status of the processes using the following command:

  1. pm2 ls

The output will match the following table:

Output┌─────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐ │ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │ ├─────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤ │ 0 │ index │ default │ 1.0.0 │ cluster │ 7932 │ 5m │ 0 │ online │ 0% │ 56.6mb │ nod… │ disabled │ │ 1 │ index │ default │ 1.0.0 │ cluster │ 7939 │ 5m │ 0 │ online │ 0% │ 55.7mb │ nod… │ disabled │ │ 2 │ index │ default │ 1.0.0 │ cluster │ 7946 │ 5m │ 0 │ online │ 0% │ 56.5mb │ nod… │ disabled │ │ 3 │ index │ default │ 1.0.0 │ cluster │ 7953 │ 5m │ 0 │ online │ 0% │ 55.9mb │ nod… │ disabled │ └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘

Now that the cluster is running, enter the following command in the same terminal to test its performance:

  1. loadtest -n 1200 -c 200 -k http://localhost:3000/heavy

The output will closely match the following:

Output

请求:0 (0%),每秒请求数:0,平均延迟:0 毫秒

目标URL: http://localhost:3000/heavy
最大请求数: 1200
并发级别: 200
代理: keepalive

已完成请求: 1200
总错误数: 0
总时间: 3.771868785 秒
每秒请求数: 318
平均延迟: 574.4 毫秒

在一定时间内完成的请求百分比
50% 216 毫秒
90% 2859 毫秒
95% 3016 毫秒
99% 3028 毫秒
100% 3084 毫秒(最长请求)

总时间、每秒请求数和平均延迟与使用集群模块时生成的指标接近。这种一致性表明pm2的扩展工作方式类似。

为了改善使用pm2的工作流程,您可以生成一个配置文件来传递应用程序的配置设置。这种方法允许您无需传递选项即可启动或重启集群。

要使用配置文件,请删除当前集群:

  1. pm2 delete index.js

您将收到一个已删除的响应:

输出[PM2] 正在应用 deleteProcessId 操作到应用 [index.js](ids: [ 0, 1, 2, 3 ]) [PM2] [index](2) ✓ [PM2] [index](1) ✓ [PM2] [index](0) ✓ [PM2] [index](3) ✓ ┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐ │ id │ 名称 │ 模式 │ ↺ │ 状态 │ cpu │ 内存 │ └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘

接下来,生成一个配置文件:

  1. pm2 ecosystem

输出将确认文件已生成:

输出文件 /home/sammy/cluster_demo/ecosystem.config.js 已生成

将.js重命名为.cjs以启用对ES模块的支持:

  1. mv ecosystem.config.js ecosystem.config.cjs

使用您的编辑器,打开配置文件:

  1. nano ecosystem.config.cjs

在您的ecosystem.config.cjs文件中,添加下面高亮显示的代码:

cluster_demo/ecosystem.config.cjs

module.exports = {
  apps : [{
    script: 'index.js',
    watch: '.',
    name: "cluster_app",
    instances: 0,
    exec_mode: "cluster",
  }, {
    script: './service-worker/',
    watch: ['./service-worker']
  }],

  deploy : {
    production : {
      user : 'SSH_USERNAME',
      host : 'SSH_HOSTMACHINE',
      ref  : 'origin/master',
      repo : 'GIT_REPOSITORY',
      path : 'DESTINATION_PATH',
      'pre-deploy-local': '',
      'post-deploy' : 'npm install && pm2 reload ecosystem.config.cjs --env production',
      'pre-setup': ''
    }
  }
};

script选项接受将在pm2包生成的每个进程中运行的文件。name属性接受任何可以标识集群的名称,这在您需要停止、重启或执行其他操作时会有帮助。instances属性接受您想要的实例数量。将instances设置为0将使pm2生成与CPU数量相同的进程。exec_mode接受cluster选项,这告诉pm2以集群模式运行。

完成后,保存并关闭您的文件。

要启动集群,请运行以下命令:

  1. pm2 start ecosystem.config.cjs

您将收到以下响应:

输出[PM2][WARN] 应用程序 cluster_app, service-worker 未运行,正在启动... [PM2][ERROR] 错误:未找到脚本:/home/node-user/cluster_demo/service-worker [PM2] 应用程序 [cluster_app] 已启动(4个实例)

最后一行确认有4个进程正在运行。由于您在本教程中尚未创建service-worker脚本,因此可以忽略关于未找到service-worker的错误。

要确认集群正在运行,请检查状态:

  1. pm2 ls

您将收到一个确认四个进程正在运行的响应:

输出
┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
│ id │ 名称               │ 模式     │ ↺    │ 状态      │ CPU      │ 内存     │
├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
│ 0  │ cluster_app        │ cluster  │ 0    │ online    │ 0%       │ 56.9mb   │
│ 1  │ cluster_app        │ cluster  │ 0    │ online    │ 0%       │ 57.6mb   │
│ 2  │ cluster_app        │ cluster  │ 0    │ online    │ 0%       │ 55.9mb   │
│ 3  │ cluster_app        │ cluster  │ 0    │ online    │ 0%       │ 55.9mb   │
└────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘

如果你想重启集群,可以使用你在 ecosystem.config.cjs 文件中定义的应用名称,在本例中是 cluster_app:

  1. pm2 restart cluster_app

集群将重启:

输出使用 --update-env 来更新环境变量 [PM2] 正在应用 restartProcessId 操作到应用 [cluster_app](ids: [ 0, 1, 2, 3 ]) [PM2] [cluster_app](0) ✓ [PM2] [cluster_app](1) ✓ [PM2] [cluster_app](2) ✓ [PM2] [cluster_app](3) ✓ ┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐ │ id │ 名称 │ 模式 │ ↺ │ 状态 │ CPU │ 内存 │ ├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤ │ 0 │ cluster_app │ cluster │ 1 │ online │ 0% │ 48.0mb │ │ 1 │ cluster_app │ cluster │ 1 │ online │ 0% │ 47.9mb │ │ 2 │ cluster_app │ cluster │ 1 │ online │ 0% │ 38.8mb │ │ 3 │ cluster_app │ cluster │ 1 │ online │ 0% │ 31.5mb │ └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘

要继续管理你的集群,可以运行以下命令:

命令 描述
pm2 start app_name 启动集群
pm2 restart app_name 终止集群并重新启动
pm2 reload app_name 无停机时间重启集群
pm2 stop app_name 停止集群
pm2 delete app_name 删除集群

现在你可以使用 pm2 模块和集群模块来扩展你的应用程序。

结论

在本教程中,你使用集群模块扩展了你的应用程序。首先,你创建了一个不使用集群模块的程序。然后,你创建了一个使用集群模块将应用程序扩展到机器上多个CPU的程序。之后,你比较了使用集群模块和不使用集群模块的应用程序之间的性能。最后,你使用 pm2 包作为集群模块的替代方案,将应用程序扩展到多个CPU上。

为了进一步学习,你可以访问集群模块文档页面了解更多关于该模块的信息。

如果你想继续使用 pm2,可以参考 PM2 - 进程管理文档。你也可以尝试我们关于如何在 Ubuntu 22.04 上为生产环境设置 Node.js 应用程序的 pm2 教程。

Node.js 还附带了 worker_threads 模块,该模块允许你将 CPU 密集型任务分配给工作线程,以便它们能够快速完成。尝试我们关于如何在 Node.js 中使用多线程的教程。你还可以使用专用的 Web Workers 优化前端的 CPU 密集型任务,这可以通过遵循如何使用 Web Workers 处理 CPU 密集型任务教程来实现。如果你想了解如何避免 CPU 密集型任务影响应用程序的请求/响应周期,请查看如何使用 Node.js 和 BullMQ 处理异步任务。