Node.js 如何利用多核 CPU

Node.js 单线程默认只用一个核,利用多核的方案:cluster 模块 fork 多个 worker 共享端口、worker_threads 处理 CPU 密集计算、pm2 的 cluster 模式一键多进程,或容器编排层做水平扩容。

最佳实践
Node.js 事件循环与异步任务示意

单个 JavaScript 主线程一次只在一个 CPU 核上执行,但 Node.js 进程也会使用其他内部线程。 需要并行执行 JavaScript 任务时, 吃满多核有三条路:① cluster 模块按核数 fork 多个进程共享同一端口;② worker_threads 在进程内开真正并行的线程做 CPU 密集计算;③ 用 pm2 等进程管理器的集群模式(本质是封装 cluster)。

cluster:多进程共享端口

const cluster = require('node:cluster');
const os = require('node:os');

if (cluster.isPrimary) {
  const numCPUs = Math.min(4, os.availableParallelism()); // 演示上限,生产按资源预算设置
  for (let i = 0; i < numCPUs; i++) cluster.fork();

  cluster.on('exit', (worker) => {
    console.log(`worker ${worker.process.pid} 退出`);
    // 生产需区分计划退出和崩溃,并配置退避、重试上限与优雅关闭
  });
} else {
  const http = require('node:http');
  http.createServer((req, res) => {
    res.end(`来自进程 ${process.pid} 的响应`);
  }).listen(3000);
}

除 Windows 外,cluster 默认通常由 primary 接收连接并轮询分发;也可使用共享监听句柄方式交由操作系统调度,并非 Linux 默认始终由内核分发。

worker_threads:进程内真并行

适合图像处理、加密、大数据计算这类 CPU 密集任务:

const { Worker } = require('node:worker_threads');
function runTask(input) {
  return new Promise((resolve, reject) => {
    const worker = new Worker('./heavy-task.js', { workerData: input });
    worker.once('message', resolve);
    worker.once('error', reject);
    worker.once('exit', code => {
      if (code !== 0) reject(new Error(`Worker exited: ${code}`));
    });
  });
}
runTask(10).then(console.log).catch(console.error);

对应 heavy-task.js 示例:

const { parentPort, workerData } = require('node:worker_threads');
parentPort.postMessage(workerData * workerData);

与 cluster 的区别:worker_threads 的普通 JS 对象和堆各自隔离,可显式共享 SharedArrayBuffer,适合计算;cluster 是独立进程,适合分摊请求。

pm2 一行搞定

pm2 start app.js -i max   # 按核数起实例,自动负载均衡
pm2 reload app            # 尝试滚动重载,需应用优雅退出/连接处理,不能保证零中断

常见问题(FAQ)

Q:fork 多少个 worker 合适?

先以 os.availableParallelism() 为参考,再按 CPU 配额、内存与负载测试限制数量;并非固定等于物理核数。此 API 需 Node 18.14+/19.4+。

Q:cluster 里共享状态怎么办?

各 worker 内存独立,session、计数器等共享状态要放 Redis 等外部存储,不能放进程内存。

Q:什么任务该用 worker_threads 而不是 cluster?

CPU 密集的 JavaScript 可用 worker_threads;已有异步压缩、crypto 等原生 API 可能已使用 libuv 线程池,不应一概再包 Worker;分摊网络请求用 cluster。两类问题,两个工具。

参考资料

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台