何时用
Node 单进程只能用一个 CPU 核——多核机器跑 HTTP 服务时浪费。cluster 让你在多个进程之间共享同一个端口。
master 进程
├── worker 1 (跑 HTTP server)
├── worker 2 (跑 HTTP server)
├── worker 3
└── worker 4
↓
所有 worker 共享一个端口 → 内核负载均衡
基础
import cluster from 'cluster';
import http from 'http';
import os from 'os';
if (cluster.isPrimary) {
const numCPUs = os.cpus().length;
console.log(`Master ${process.pid} starting ${numCPUs} workers`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died, restarting`);
cluster.fork();
});
} else {
// worker 进程
http.createServer((req, res) => {
res.end(`Hello from worker ${process.pid}\n`);
}).listen(8080);
}
跑:
node app.js
# Master 12345 starting 8 workers
# 8 个 worker 共享 8080 端口
主从通信
// master 发给 worker
worker.send({ task: 'reload-config' });
// worker 收
process.on('message', msg => {
if (msg.task === 'reload-config') reloadConfig();
});
// worker 发给 master
process.send({ status: 'ready' });
// master 收
cluster.on('message', (worker, msg) => {
console.log(`worker ${worker.id} says:`, msg);
});
优雅重启(zero-downtime)
function reload() {
const workers = Object.values(cluster.workers);
workers.forEach((worker, i) => {
setTimeout(() => {
worker.disconnect(); // 不再接新连接
worker.on('exit', () => {
cluster.fork(); // 新起一个
});
}, i * 1000); // 错开重启
});
}
process.on('SIGUSR2', reload); // kill -SIGUSR2 <pid> 触发
cluster vs PM2
实战几乎都用 PM2 而不是自己写 cluster——它内置 cluster 模式 + 监控 + 日志:
pm2 start app.js -i max # max = 自动按 CPU 核数
pm2 start app.js -i 4 # 固定 4 个
pm2 reload app # 优雅重启
PM2 帮你:
- 自动重启崩溃的进程
- 滚动重启(一个一个换)
- 监控仪表板
- 日志聚合
生产推荐 PM2——自己写 cluster 主要用于学习 / 特殊场景。
cluster vs 反向代理
方案 A:Node cluster(同一进程组)
互联网 → Node cluster → workers
方案 B:反向代理(多进程独立)
互联网 → Nginx → Node:3001
↓ Node:3002
↓ Node:3003
方案 B 优点:
- 进程隔离更彻底(一个挂不影响别的)
- Node 升级 / 重启更灵活
- 反向代理还能做静态文件 / 缓存 / 限流
多数生产用方案 B(PM2 + Nginx)—— cluster 适合不想要反向代理的简单场景。
共享 state 的麻烦
// ❌ 每个 worker 一份独立 counter,互不知道
let counter = 0;
http.createServer((req, res) => {
counter++;
res.end(`Count: ${counter}`);
}).listen(8080);
要"全局"状态用:
- Redis(共享 counter / session)
- 数据库
- 共享文件(不推荐)
worker_threads vs cluster
| worker_threads | cluster | |
|---|---|---|
| 隔离 | 同进程,共享内存 | 独立进程 |
| 启动开销 | 低 | 中 |
| 内存 | 共享 V8 | 每个独立 V8 |
| 适合 | CPU 密集计算 | 并发 HTTP 处理 |
坑
- 不是所有应用都该用 cluster——IO 密集(多数 Web)用 cluster 受益不大(单进程也能扛很多)
- worker 之间不共享 cache / state——重新设计架构
- cluster + SQLite 容易锁竞争——多进程数据库选 Postgres
- 实战首选 PM2,自己写 cluster 容易出错
下一篇:crypto。