理解 NodeJS 集群模块(1/4)
这篇文章最初发表于“A Curious Animal”博客。
NodeJS 进程在单进程上运行,这意味着它默认不会利用多核系统的优势。如果你有一个 8 核 CPU,并通过$ node app.js
它运行 NodeJS 程序,它将在单进程中运行,浪费剩余的 CPU 资源。
值得庆幸的是,NodeJS 提供了cluster模块,其中包含一组函数和属性,可以帮助我们创建充分利用所有 CPU 的程序。cluster 模块用于最大化 CPU 利用率的机制是通过 fork 进程来实现的,这类似于 Unix 系统中旧的fork()系统调用。
有关本系列的更多信息:
- 了解 NodeJS 集群模块
- 使用集群模块和 HTTP 服务器
- 使用 PM2 管理 NodeJS 集群
- 使用 PM2 时优雅关闭 NodeJS HTTP 服务器
集群模块介绍
集群模块是一个 NodeJS 模块,它包含一组函数和属性,帮助我们分叉进程以利用多核系统。这可能是你在 Node 应用程序中必须关注的第一级可扩展性,特别是如果你正在开发 HTTP 服务器应用程序,在进入更高级别的可扩展性(我指的是在不同机器上进行垂直和水平扩展)之前。
使用 cluster 模块,父进程/主进程可以 fork 出任意数量的子进程/工作进程,并通过IPC通信与它们发送消息进行通信。请记住,进程之间没有共享内存。
接下来几行是从 NodeJS 文档中汇编出来的句子,我冒昧地复制并粘贴了这些句子,以便用几行文字帮助您理解整个内容。
Node.js 的单个实例在单线程中运行。为了充分利用多核系统,用户有时需要启动一个 Node.js 进程集群来处理负载。
集群模块允许轻松创建共享服务器端口的子进程。
使用 方法来创建工作进程(子进程)
child_proces.fork()
,以便它们可以通过 IPC 与父进程通信并来回传递服务器句柄。child_process.fork()
方法是 的一个特例,child_process.spawn()
专门用于创建新的 Node.js 进程。与 类似child_process.spawn()
,ChildProcess
它会返回一个对象。返回的对象ChildProcess
将内置一个额外的通信通道,允许通过 方法来在父进程和子进程之间来回传递消息send()
。subprocess.sen()
详情请参阅。需要注意的是,生成的 Node.js 子进程与父进程无关,除了两者之间建立的 IPC 通信通道。每个进程都有自己的内存和 V8 实例。由于需要分配额外的资源,因此不建议生成大量 Node.js 子进程。
所以,大部分神奇的功能都由child_process模块完成,该模块负责创建新的进程并帮助它们之间进行通信,例如创建管道。你可以在Node.js 子进程:你需要知道的一切中找到一篇很棒的文章。
一个基本的例子
废话不多说,我们来看一个实际的例子。接下来我们展示一段非常基础的代码:
- 创建一个主进程,用于检索 CPU 数量并为每个 CPU 创建一个工作进程,并且
- 每个子进程在控制台中打印一条消息并退出。
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
masterProcess();
} else {
childProcess();
}
function masterProcess() {
console.log(`Master ${process.pid} is running`);
for (let i = 0; i < numCPUs; i++) {
console.log(`Forking process number ${i}...`);
cluster.fork();
}
process.exit();
}
function childProcess() {
console.log(`Worker ${process.pid} started and finished`);
process.exit();
}
将代码保存在app.js
文件中并运行执行:$ node app.js
。输出应该类似于以下内容:
$ node app.js
Master 8463 is running
Forking process number 0...
Forking process number 1...
Forking process number 2...
Forking process number 3...
Worker 8464 started and finished
Worker 8465 started and finished
Worker 8467 started and finished
Worker 8466 started and finished
代码解释
当我们运行app.js
程序时,会创建一个操作系统进程,并开始运行我们的代码。一开始会导入集群模式const cluster = require('cluster')
,并在if
语句中检查isMaster
属性是否正确。
因为该进程是第一个进程,所以isMaster
属性是,true
然后我们运行函数的代码masterProcess
。这个函数没什么秘密,它根据机器的 CPU 数量进行循环,并使用该cluster.fork()
方法 fork 当前进程。
真正做fork()
的是创建一个新的节点进程,就像您通过命令行运行它一样$node app.js
,也就是说您有许多进程在运行您的app.js
程序。
当子进程创建并执行时,它会执行与主进程相同的操作,即导入 cluster 模块并执行if
语句。区别之一是,对于子进程来说, 的值为cluster.isMaster
,false
因此它们会结束childProcess
函数的运行。
请注意,我们使用明确终止主进程和工作进程process.exit()
,默认情况下返回值为零。
注意:NodeJS 还提供了子进程模块,简化了创建进程以及与其他进程的通信。例如,我们可以生成
ls -l
终端命令,并通过管道连接到另一个进程来处理结果。
主进程和工作进程之间的通信
创建工作进程时,会在工作进程和主进程之间创建一个 IPC 通道,允许我们send()
使用接受 JavaScript 对象作为参数的方法在它们之间进行通信。请记住,它们是不同的进程(而不是线程),因此我们不能使用共享内存作为通信方式。
从主进程,我们可以使用进程引用(即)向工作进程发送消息someChild.send({ ... })
,而在工作进程内,我们只需使用当前process
引用(即)即可向主进程发送消息process.send()
。
我们对之前的代码做了一些更新,以允许主进程与工作进程之间发送和接收消息,并且工作进程也可以从主进程接收和发送消息:
function childProcess() {
console.log(`Worker ${process.pid} started`);
process.on('message', function(message) {
console.log(`Worker ${process.pid} recevies message '${JSON.stringify(message)}'`);
});
console.log(`Worker ${process.pid} sends message to master...`);
process.send({ msg: `Message from worker ${process.pid}` });
console.log(`Worker ${process.pid} finished`);
}
工作进程很容易理解。首先,我们message
使用 方法来注册一个监听器来监听事件process.on('message', handler)
。然后我们使用 发送消息process.send({...})
。注意,消息是一个普通的 JavaScript 对象。
let workers = [];
function masterProcess() {
console.log(`Master ${process.pid} is running`);
// Fork workers
for (let i = 0; i < numCPUs; i++) {
console.log(`Forking process number ${i}...`);
const worker = cluster.fork();
workers.push(worker);
// Listen for messages from worker
worker.on('message', function(message) {
console.log(`Master ${process.pid} recevies message '${JSON.stringify(message)}' from worker ${worker.process.pid}`);
});
}
// Send message to the workers
workers.forEach(function(worker) {
console.log(`Master ${process.pid} sends message to worker ${worker.process.pid}...`);
worker.send({ msg: `Message from master ${process.pid}` });
}, this);
}
该masterProcess
函数分为两部分。在第一个循环中,我们 fork 与 CPU 数量相同的工作进程。cluster.fork()
返回一个worker
表示工作进程的对象,我们将该引用存储在一个数组中,并注册一个监听器来接收来自该工作进程实例的消息。
稍后,我们循环遍历工作者数组并从主进程向该具体工作者发送消息。
如果你运行代码,输出将类似于:
$ node app.js
Master 4045 is running
Forking process number 0...
Forking process number 1...
Master 4045 sends message to worker 4046...
Master 4045 sends message to worker 4047...
Worker 4047 started
Worker 4047 sends message to master...
Worker 4047 finished
Master 4045 recevies message '{"msg":"Message from worker 4047"}' from worker 4047
Worker 4047 recevies message '{"msg":"Message from master 4045"}'
Worker 4046 started
Worker 4046 sends message to master...
Worker 4046 finished
Master 4045 recevies message '{"msg":"Message from worker 4046"}' from worker 4046
Worker 4046 recevies message '{"msg":"Message from master 4045"}'
这里我们不终止进程,process.exit()
因此关闭您需要使用的应用程序ctrl+c
。
结论
集群模块为 NodeJS 提供了充分利用 CPU 能力所需的功能。虽然本文未提及,但集群模块与子进程模块相辅相成,后者提供了丰富的进程操作工具:启动、停止、管道输入/输出等。
Cluster 模块允许我们轻松创建工作进程。此外,它还神奇地创建了一个 IPC 通道,用于通过 JavaScript 对象在主进程和工作进程之间进行通信。
在我的下一篇文章中,我将展示集群模块在 HTTP 服务器中的重要性,无论是使用 ExpressJS 的 API 还是 Web 服务器。集群模块可以提高应用程序的性能,使其拥有与 CPU 核心数量相同的工作进程。
文章来源:https://dev.to/acanimal/understanding-the-nodejs-cluster-module-14-2bi