Node.js 默认运行在单线程上,这意味着无论你的 CPU 有多少个核心,你的应用都只能使用其中一个。但此刻,正是 Node.js cluster 模块像超级英雄一样闪亮登场拯救世界的时刻!
可以这样理解集群:想象一下,在一个繁忙的商店里,你不是只有一个收银员忙得不可开交,而是雇佣了多个收银员(工作进程),他们都在一位经理(主进程)的管理下协同工作。结果如何?客户满意度飙升,长队不再出现!
集群革命:从零到英雄 🚀
让我们从经典的集群"Hello World"示例开始:
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`主进程 ${process.pid} 正在运行 🎯`);
// 创建与 CPU 核心数相等的工作进程
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`工作进程 ${worker.process.pid} 已终止 💀`);
console.log('正在启动新工作进程... 🔄');
cluster.fork();
});
} else {
// 工作进程可以共享任意 TCP 端口
http.createServer((req, res) => {
res.writeHead(200);
res.end(`来自工作进程 ${process.pid} 的问候! 👋\n`);
}).listen(8000);
console.log(`工作进程 ${process.pid} 已启动 🎉`);
}
就这样,你将单线程应用变成了多核性能猛兽!
主进程与工作进程的协作之舞 💃🕺
理解主进程和工作进程之间的关系至关重要。这就像一场精心编排的舞蹈:
主进程(指挥家)
主进程是你集群交响乐的指挥。它不处理 HTTP 请求,而是像老板一样管理工作者:
const cluster = require('cluster');
const os = require('os');
class ClusterManager {
constructor() {
this.workers = new Map();
this.maxWorkers = os.cpus().length;
}
start() {
if (!cluster.isMaster) return;
console.log(`🎭 主进程 ${process.pid} 正在启动 ${this.maxWorkers} 个工作进程`);
// 生成初始工作进程
for (let i = 0; i < this.maxWorkers; i++) {
this.spawnWorker();
}
// 处理工作进程事件
cluster.on('exit', (worker, code, signal) => {
this.handleWorkerExit(worker, code, signal);
});
cluster.on('online', (worker) => {
console.log(`🎉 工作进程 ${worker.process.pid} 已上线!`);
});
}
spawnWorker() {
const worker = cluster.fork();
this.workers.set(worker.id, {
worker,
startTime: Date.now(),
requests: 0
});
return worker;
}
handleWorkerExit(worker, code, signal) {
console.log(`💀 工作进程 ${worker.process.pid} 已终止 (${signal || code})`);
this.workers.delete(worker.id);
// 如果不是故意终止的,则重新生成工作进程
if (!worker.exitedAfterDisconnect) {
console.log('🔄 正在生成替代工作进程...');
this.spawnWorker();
}
}
gracefulShutdown() {
console.log('🛑 正在启动优雅关闭...');
for (const workerInfo of this.workers.values()) {
workerInfo.worker.disconnect();
}
setTimeout(() => {
console.log('⚡ 正在强制终止剩余工作进程...');
for (const workerInfo of this.workers.values()) {
workerInfo.worker.kill();
}
}, 10000);
}
}
if (cluster.isMaster) {
const manager = new ClusterManager();
manager.start();
// 处理优雅关闭
process.on('SIGTERM', () => manager.gracefulShutdown());
process.on('SIGINT', () => manager.gracefulShutdown());
}
工作进程(主力军)
工作进程是魔法发生的地方。它们处理实际的 HTTP 请求:
const express = require('express');
const cluster = require('cluster');
if (cluster.isWorker) {
const app = express();
let requestCount = 0;
app.use((req, res, next) => {
requestCount++;
console.log(`🔥 工作进程 ${process.pid} 正在处理第 ${requestCount} 个请求`);
next();
});
app.get('/', (req, res) => {
// 模拟一些工作
const start = Date.now();
while (Date.now() - start < 100) {
// 繁忙等待 100 毫秒
}
res.json({
message: '来自集群的问候!',
worker: process.pid,
requests: requestCount,
timestamp: new Date().toISOString()
});
});
app.get('/heavy', (req, res) => {
// 模拟 CPU 密集型任务
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += Math.random();
}
res.json({
result,
worker: process.pid,
message: '繁重计算完成!'
});
});
const server = app.listen(3000, () => {
console.log(`🚀 工作进程 ${process.pid} 正在监听端口 3000`);
});
// 优雅关闭处理
process.on('SIGTERM', () => {
console.log(`🛑 工作进程 ${process.pid} 收到 SIGTERM`);
server.close(() => {
process.exit(0);
});
});
}
负载均衡的魔法:请求如何被分发 🎯
Node.js 集群默认使用轮询方式(Windows 除外)。就像有一个交通警察将车辆引导到不同的车道:
const cluster = require('cluster');
const express = require('express');
if (cluster.isMaster) {
const numWorkers = 4;
console.log(`🎪 正在设置 ${numWorkers} 个工作进程...`);
for (let i = 0; i < numWorkers; i++) {
const worker = cluster.fork();
worker.on('message', (message) => {
if (message.type === 'stats') {
console.log(`📊 工作进程 ${worker.process.pid}: ${message.requests} 个请求`);
}
});
}
// 向所有工作进程广播
setInterval(() => {
for (const id in cluster.workers) {
cluster.workers[id].send({ type: 'ping' });
}
}, 10000);
} else {
const app = express();
let requestCount = 0;
app.get('/', (req, res) => {
requestCount++;
res.json({
worker: process.pid,
requestNumber: requestCount,
message: '负载均衡的请求!'
});
});
app.listen(3000);
// 定期向主进程发送统计信息
setInterval(() => {
process.send({
type: 'stats',
requests: requestCount
});
}, 5000);
}
高级集群策略 🧠
1. 粘性会话(当需要保持状态时)
有时你需要来自同一客户端的请求命中同一个工作进程:
const cluster = require('cluster');
const http = require('http');
const crypto = require('crypto');
if (cluster.isMaster) {
const workers = [];
const numWorkers = 4;
// 创建工作进程
for (let i = 0; i < numWorkers; i++) {
workers.push(cluster.fork());
}
const server = http.createServer((req, res) => {
// 基于哈希的简单粘性会话
const sessionId = req.headers['x-session-id'] ||
crypto.createHash('md5').update(req.connection.remoteAddress).digest('hex');
const workerIndex = parseInt(sessionId, 16) % numWorkers;
const worker = workers[workerIndex];
// 将请求传递给特定工作进程
worker.send({
type: 'request',
url: req.url,
method: req.method,
headers: req.headers,
sessionId
});
worker.once('message', (response) => {
if (response.type === 'response') {
res.writeHead(response.statusCode, response.headers);
res.end(response.body);
}
});
});
server.listen(3000);
}
2. 工作进程专业化(不同的工作进程处理不同任务)
并非所有工作进程都需要做同样的事情:
const cluster = require('cluster');
if (cluster.isMaster) {
// HTTP 工作进程
for (let i = 0; i < 2; i++) {
const worker = cluster.fork({ WORKER_TYPE: 'http' });
console.log(`🌐 HTTP 工作进程 ${worker.process.pid} 已启动`);
}
// 后台作业工作进程
for (let i = 0; i < 2; i++) {
const worker = cluster.fork({ WORKER_TYPE: 'background' });
console.log(`⚙️ 后台工作进程 ${worker.process.pid} 已启动`);
}
} else {
const workerType = process.env.WORKER_TYPE;
if (workerType === 'http') {
// 处理 HTTP 请求
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.json({ message: 'HTTP 工作进程响应', pid: process.pid });
});
app.listen(3000);
} else if (workerType === 'background') {
// 处理后台作业
setInterval(() => {
console.log(`🔄 后台工作进程 ${process.pid} 正在处理作业...`);
// 在此处理后台任务
}, 5000);
}
}
3. 动态工作进程扩展(自动扩展魔法)
基于负载扩展工作进程:
const cluster = require('cluster');
const os = require('os');
class AutoScaler {
constructor() {
this.maxWorkers = os.cpus().length * 2;
this.minWorkers = 2;
this.currentLoad = 0;
this.workers = new Set();
}
start() {
if (!cluster.isMaster) return;
// 从最小工作进程数开始
for (let i = 0; i < this.minWorkers; i++) {
this.addWorker();
}
// 每 30 秒监控一次负载
setInterval(() => this.checkLoad(), 30000);
}
addWorker() {
if (this.workers.size >= this.maxWorkers) return;
const worker = cluster.fork();
this.workers.add(worker);
worker.on('exit', () => {
this.workers.delete(worker);
});
worker.on('message', (msg) => {
if (msg.type === 'load') {
this.updateLoad(msg.load);
}
});
console.log(`📈 已扩展: ${this.workers.size} 个工作进程`);
}
removeWorker() {
if (this.workers.size <= this.minWorkers) return;
const worker = this.workers.values().next().value;
worker.disconnect();
this.workers.delete(worker);
console.log(`📉 已缩减: ${this.workers.size} 个工作进程`);
}
updateLoad(load) {
this.currentLoad = load;
}
checkLoad() {
console.log(`📊 当前负载: ${this.currentLoad}%`);
if (this.currentLoad > 80) {
this.addWorker();
} else if (this.currentLoad < 30 && this.workers.size > this.minWorkers) {
this.removeWorker();
}
}
}
if (cluster.isMaster) {
const scaler = new AutoScaler();
scaler.start();
} else {
// 带有负载报告的工作进程
const express = require('express');
const app = express();
let requestCount = 0;
const startTime = Date.now();
app.use((req, res, next) => {
requestCount++;
next();
});
app.get('/', (req, res) => {
res.json({ worker: process.pid, requests: requestCount });
});
app.listen(3000);
// 向主进程报告负载
setInterval(() => {
const uptime = (Date.now() - startTime) / 1000;
const load = (requestCount / uptime) * 100; // 简化的负载计算
process.send({ type: 'load', load });
}, 10000);
}
监控你的集群大军 📊
知识就是力量!以下是监控集群的方法:
const cluster = require('cluster');
const EventEmitter = require('events');
class ClusterMonitor extends EventEmitter {
constructor() {
super();
this.stats = {
totalRequests: 0,
workers: new Map()
};
}
start() {
if (!cluster.isMaster) return;
setInterval(() => this.printStats(), 10000);
cluster.on('message', (worker, message) => {
this.handleWorkerMessage(worker, message);
});
}
handleWorkerMessage(worker, message) {
if (message.type === 'stats') {
this.stats.workers.set(worker.id, {
pid: worker.process.pid,
requests: message.requests,
memory: message.memory,
uptime: message.uptime
});
this.stats.totalRequests += message.requests;
}
}
printStats() {
console.log('\n🔍 集群统计仪表板');
console.log('═'.repeat(50));
console.log(`📊 总请求数: ${this.stats.totalRequests}`);
console.log(`👥 活跃工作进程: ${this.stats.workers.size}`);
this.stats.workers.forEach((stats, workerId) => {
const memoryMB = Math.round(stats.memory.rss / 1024 / 1024);
console.log(` 工作进程 ${stats.pid}: ${stats.requests} 个请求, ${memoryMB}MB, ${Math.round(stats.uptime)}秒`);
});
console.log('═'.repeat(50));
}
}
if (cluster.isMaster) {
const monitor = new ClusterMonitor();
monitor.start();
// 生成工作进程
for (let i = 0; i < 4; i++) {
cluster.fork();
}
} else {
const express = require('express');
const app = express();
let requestCount = 0;
const startTime = Date.now();
app.get('/', (req, res) => {
requestCount++;
res.json({ message: '来自集群的问候!', worker: process.pid });
});
app.listen(3000);
// 向主进程发送统计信息
setInterval(() => {
process.send({
type: 'stats',
requests: requestCount,
memory: process.memoryUsage(),
uptime: (Date.now() - startTime) / 1000
});
requestCount = 0; // 重置计数器
}, 5000);
}
常见的集群陷阱(以及如何避免)⚠️
1. 共享状态陷阱
记住:工作进程不共享内存!使用 Redis 或数据库处理共享状态:
// ❌ 不要这样做
let globalCounter = 0;
app.get('/count', (req, res) => {
globalCounter++; // 这在不同工作进程间不会生效!
res.json({ count: globalCounter });
});
// ✅ 应该这样做
const redis = require('redis');
const client = redis.createClient();
app.get('/count', async (req, res) => {
const count = await client.incr('global_counter');
res.json({ count });
});
2. 端口绑定冲突
只有主进程应该为外部服务绑定端口:
// ❌ 不要在工作进程中这样做
if (cluster.isWorker) {
// 这会导致端口冲突!
redis.createServer().listen(6379);
}
// ✅ 只在主进程中这样做
if (cluster.isMaster) {
redis.createServer().listen(6379);
}
3. 优雅关闭的挑战
始终正确处理关闭:
if (cluster.isWorker) {
const server = app.listen(3000);
process.on('SIGTERM', () => {
console.log('🛑 正在启动优雅关闭...');
server.close(() => {
console.log('✅ HTTP 服务器已关闭');
process.exit(0);
});
// 10 秒后强制退出
setTimeout(() => {
console.log('⚡ 正在强制退出...');
process.exit(1);
}, 10000);
});
}
性能优化秘诀 🚀
1. 工作进程池大小调整
不要只使用 os.cpus().length。考虑你的工作负载类型:
function getOptimalWorkerCount() {
const cpuCount = os.cpus().length;
const workloadType = process.env.WORKLOAD_TYPE || 'mixed';
switch (workloadType) {
case 'cpu-intensive':
return cpuCount; // 每个 CPU 一个工作进程
case 'io-intensive':
return cpuCount * 2; // 更多工作进程处理 I/O 等待
case 'mixed':
default:
return Math.max(2, cpuCount); // 至少 2 个,最多 CPU 核心数
}
}
2. 内存使用优化
监控并限制内存使用:
if (cluster.isWorker) {
setInterval(() => {
const usage = process.memoryUsage();
const memoryMB = usage.rss / 1024 / 1024;
if (memoryMB > 500) { // 500MB 限制
console.log(`⚠️ 工作进程 ${process.pid} 使用了 ${memoryMB}MB,正在重启...`);
process.exit(1); // 主进程会重启我们
}
}, 30000);
}
生产环境部署策略 🏭
Docker + 集群组合
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# 使用集群实现多核利用
CMD ["node", "cluster.js"]
PM2 替代方案
虽然 PM2 很棒,但理解原生集群能给你更多控制权:
// 纯 Node.js 中的 ecosystem.config.js 等效实现
const cluster = require('cluster');
if (cluster.isMaster) {
const config = {
instances: process.env.NODE_ENV === 'production' ? 'max' : 2,
maxMemoryRestart: '1G',
nodeArgs: '--max-old-space-size=1024'
};
const workerCount = config.instances === 'max' ?
require('os').cpus().length : config.instances;
for (let i = 0; i < workerCount; i++) {
cluster.fork();
}
}
集群的核心价值 🎯
Node.js 集群是你释放服务器全部潜力的门票。在处理流量时,这就像是三轮车和怪物卡车的区别!
关键要点:
-
使用所有 CPU 核心,而不仅仅是一个
-
主进程负责管理,工作进程处理请求
-
负载均衡自动进行
-
始终处理优雅关闭
-
监控集群健康状况
-
调整工作进程大小时考虑工作负载类型
从一个简单的集群设置开始,然后随着需求的增长逐渐添加监控、自动扩展和专业化工作进程。你未来的自己(和你的服务器账单)会感谢你的!


被折叠的 条评论
为什么被折叠?



