介绍
单个 Node.js 程序的实例仅在一个线程上运行,因此无法充分利用 CPU 的多核系统。有时你可能需要启动 Node.js 进程集群来利用本地计算机或生产服务器上的每个 CPU 内核。
在处理 API 或基于 ExpressJS 的HTTP服务器时,这个问题尤其重要。
幸运的是,Node.js 有一个名为 Cluster 的核心模块,它能够帮助我们在 CPU 的所有核心上运行 Node.js 程序。
在本文中,我们将会用 Node.js 实现一个 ExpressJS HTTP 服务器,并在每个 CPU 内核上创建一个唯一的实例。这样,由于每个其CPU 核心实例都会提供可能的并发请求数,因此 HTTP 服务器的吞吐量将会大大增加。
创建 Express HTTP 服务器
我们要做的第一件事是启动并运行 HTTP 服务器。如果你已经有了一个可用的 ExpressJS 服务器,则可以跳至下一部分:在多核 CPU 上运行服务器。
我们将用 ExpressJS 来快速创建一个高效而简单的服务器。如果尚未安装 npm 软件包,则可以用以下命令进行安装:
- $ npm install --save express
然后把下面的代码添加到要你的的 Node.js 文件中:
- const express = require("express")
- const PORT = process.env.PORT || 5000
- const app = express()
- app.listen(PORT, function () {
- console.log(`Express server listening on port ${PORT}`)
- })
首先,我们 require() 先前安装的 Express npm 软件包。
然后,我们创建一个 PORT 变量,该变量可以是当前的 process.env.PORT 的值,也可以是 5000。然后用express() 方法创建一个 express 实例,并将其保存在 app 变量中。
最添加 app.listen() 函数,用于启动 Express 程序,并告诉它侦听我们指定的 PORT。
通过命令行运行代码时,应该看到类似的内容输出到控制台:
- Output:
- Express server listening on port 5000
很好!现在我们启动 Express HTTP 服务器。
在多个 CPU 核心上运行服务器
在本节中,我们会把 Express 服务器运行在 CPU 的多个核心上!
为了帮助我们实现这一目标,我们将使用Node.js模块 OS 和 Cluster 。用 OS 模块来检测系统有多少个 CPU 核,用 Cluster 模块来创建多个子进程,我们的 HTTP 服务器可以并行运行这些子进程。
由于这些是核心模块,因此不需要安装任何 npm 包,并且可以将它们 require()到我们的代码中。
我将为你提供完整的代码,并在随后进行解释,因此,如果你看得一头雾水,也没有关系。
这是完整的代码:
- const express = require("express")
- const os = require("os")
- const cluster = require("cluster")
- const PORT = process.env.PORT || 5000
- const clusterWorkerSize = os.cpus().length
- if (clusterWorkerSize > 1) {
- if (cluster.isMaster) {
- for (let i=0; i < clusterWorkerSize; i++) {
- cluster.fork()
- }
- cluster.on("exit", function(worker) {
- console.log("Worker", worker.id, " has exitted.")
- })
- } else {
- const app = express()
- app.listen(PORT, function () {
- console.log(`Express server listening on port ${PORT} and worker ${process.pid}`)
- })
- }
- } else {
- const app = express()
- app.listen(PORT, function () {
- console.log(`Express server listening on port ${PORT} with the single worker ${process.pid}`)
- })
- }
代码中做了很多事情,所以让我们解释它的每一个部分。
首先是 require() express 包以及 Node.js 的两个核心模块 os 和 cluster。
接下来,创建一个 PORT 变量,并为其分配当前 process.env.PORT 编号或 5000的值。我们稍后将在启动时用到它。
然后,我们创建一个名为 clusterWorkerSize 的变量来表示系统的 CPU 数量。可以用 os.cpus().length方法获得这个数字。有关 os.cpus() 方法的更多信息可查看 Node.js 文档。
我们创建一个了 if...else语句,用 clusterWorkerSize 值检查 CPU 是否有多个核。如果 CPU 数量大于 1,我们就继续创建集群。但是如果运行代码的计算机上只有一个 CPU 核心,则以本教程第一步中的方式启动 Express 程序。
假设我们的机器有多个 CPU 核心,那么就要创建另一个 if...else 语句,检查该语句是否为集群中已运行的第一个进程。用 cluster.isMaster() 方法检查是否返回 true或 false。
如果是第一个运行的进程,我们将用 cluster.fork() 为计算机上的每个 CPU 核产生一个新的工作进程。我们还添加了一个事件侦听器,该侦听器将在工作进程退出时输出一条消息,以便我们知道何时出现问题或意外。
值得注意的是,主进程用于侦听 HTTP 服务器的端口,并在工作进程之间平衡所有请求的负载。
产生所有工作进程后,我们将在创建的每个工作进程上创建一个程序的新实例。如果你的计算机有 2 个 CPU 核,则将会创建该程序的 2 个实例。
运行程序时,应该能够在控制台上看到以下内容:
- Output:
- Express server listening on port 5000 and worker 10801
- Express server listening on port 5000 and worker 10802
- Express server listening on port 5000 and worker 10803
- Express server listening on port 5000 and worker 10804
输出将根据 CPU 核的数量而有所不同。
现在你有了一个能够在多核 CPU 上运行的 HTTP 服务器!
总结
cluster 模块使我们能够轻松创建子进程,从而为 Node.js 提供了使用 CPU 所提供的全部功能所急需的功能。并且它还在后台为在主进程和工作进程之间进行通信做了大量工作。
在读完本文之后,你现在应该知道该如何使用这个模块在多个 CPU 核心上运行 Express HTTP 服务器。有了这些知识,你将能够更好地管理和扩展你的应用。