Node.js Cluster 模块详解
Node.js 是单线程运行的,这意味着默认情况下只能利用一个 CPU 核心。对于多核服务器来说,这是巨大的资源浪费。Cluster 模块允许创建多个子进程(工作进程),充分利用多核 CPU 的性能,同时保持代码的简洁性。理解 Cluster 模块的工作原理对于构建高性能、高可用的 Node.js 应用至关重要。
Node.js Cluster 模块详解
1. 问题解析
Node.js 是单线程运行的,这意味着默认情况下只能利用一个 CPU 核心。对于多核服务器来说,这是巨大的资源浪费。Cluster 模块允许创建多个子进程(工作进程),充分利用多核 CPU 的性能,同时保持代码的简洁性。理解 Cluster 模块的工作原理对于构建高性能、高可用的 Node.js 应用至关重要。
2. 核心概念
2.1 Node.js 单线程模型
┌─────────────────────────────────────────┐
│ 单线程 Node.js 进程 │
│ ┌─────────────────────────────────┐ │
│ │ JavaScript 代码 │ │
│ │ (单线程执行) │ │
│ └─────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────┐ │
│ │ libuv 线程池 │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌────┐ │ │
│ │ │线程1│ │线程2│ │线程3│ │线程4│ │ │
│ │ └─────┘ └─────┘ └─────┘ └────┘ │ │
│ └─────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────┐ │
│ │ 操作系统(多核 CPU) │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌────┐ │ │
│ │ │核心1│ │核心2│ │核心3│ │核心4│ │ │
│ │ └─────┘ └─────┘ └─────┘ └────┘ │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
2.2 Cluster 多进程模型
┌─────────────────────────────────────────┐
│ 主进程 (Master) │
│ ┌──────────────────┐ │
│ │ 负责调度管理 │ │
│ │ - 创建/重启子进程 │ │
│ │ - 负载均衡 │ │
│ │ - 进程间通信 │ │
│ └────────┬─────────┘ │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ ↓ ↓ ↓ │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │Worker│ │Worker│ │Worker│ │
│ │ #1 │ │ #2 │ │ #3 │ │
│ └──┬───┘ └──┬───┘ └──┬───┘ │
└────┼───────────┼───────────┼───────────┘
│ │ │
└───────────┼───────────┘
↓
┌───────────────┐
│ 共享端口 │
│ (Round Robin) │
└───────────────┘
2.3 进程间关系
| 角色 | 职责 | 数量 |
|---|---|---|
| Master | 进程管理、负载均衡、不处理业务 | 1 |
| Worker | 处理 HTTP 请求、执行业务逻辑 | 通常等于 CPU 核心数 |
3. 详细解答
3.1 Cluster 基础使用
const cluster = require('cluster');
const http = require('http');
const os = require('os');
const numCPUs = os.cpus().length;
if (cluster.isMaster) {
console.log(`主进程 ${process.pid} 正在运行`);
// 创建与 CPU 核心数相同的工作进程
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
// 监听工作进程事件
cluster.on('fork', (worker) => {
console.log(`工作进程 ${worker.process.pid} 已创建`);
});
cluster.on('online', (worker) => {
console.log(`工作进程 ${worker.process.pid} 已就绪`);
});
cluster.on('listening', (worker, address) => {
console.log(`工作进程 ${worker.process.pid} 正在监听 ${address.port}`);
});
cluster.on('disconnect', (worker) => {
console.log(`工作进程 ${worker.process.pid} 已断开连接`);
});
cluster.on('exit', (worker, code, signal) => {
console.log(`工作进程 ${worker.process.pid} 已退出 (code: ${code}, signal: ${signal})`);
// 自动重启工作进程
console.log('正在创建新的工作进程...');
cluster.fork();
});
// 主进程收到消息
Object.values(cluster.workers).forEach(worker => {
worker.on('message', (msg) => {
console.log(`主进程收到来自 ${worker.process.pid} 的消息:`, msg);
});
});
} else {
// 工作进程创建 HTTP 服务器
const server = http.createServer((req, res) => {
res.writeHead(200);
res.end(`Hello from worker ${process.pid}\n`);
// 模拟工作进程向主进程发送消息
process.send({ cmd: 'notifyRequest', pid: process.pid });
});
server.listen(8000);
console.log(`工作进程 ${process.pid} 已启动`);
}
3.2 进程间通信 (IPC)
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
const workers = [];
// 创建工作进程
for (let i = 0; i < os.cpus().length; i++) {
const worker = cluster.fork();
workers.push(worker);
// 接收工作进程消息
worker.on('message', (msg) => {
if (msg.cmd === 'getSharedData') {
// 主进程查询共享数据后返回
worker.send({
cmd: 'sharedDataResponse',
data: getSharedData(msg.key)
});
} else if (msg.cmd === 'broadcast') {
// 向所有工作进程广播消息
workers.forEach(w => {
if (w.id !== worker.id) {
w.send({
cmd: 'broadcastMessage',
from: worker.id,
data: msg.data
});
}
});
}
});
}
// 模拟共享数据存储
const sharedData = new Map();
function getSharedData(key) {
return sharedData.get(key);
}
} else {
// 工作进程
const http = require('http');
// 接收主进程消息
process.on('message', (msg) => {
if (msg.cmd === 'sharedDataResponse') {
console.log(`Worker ${process.pid} 收到共享数据:`, msg.data);
} else if (msg.cmd === 'broadcastMessage') {
console.log(`Worker ${process.pid} 收到广播:`, msg.data);
}
});
const server = http.createServer((req, res) => {
if (req.url === '/get-data') {
// 向主进程请求共享数据
process.send({ cmd: 'getSharedData', key: 'config' });
res.end('Request sent to master');
} else if (req.url === '/broadcast') {
// 向所有工作进程广播
process.send({ cmd: 'broadcast', data: { message: 'Hello all!' } });
res.end('Broadcast sent');
} else {
res.end(`Worker ${process.pid}`);
}
});
server.listen(8000);
}
3.3 负载均衡策略
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
// 设置负载均衡策略
// cluster.SCHED_NONE - 由操作系统决定(默认在 Windows 上)
// cluster.SCHED_RR - Round Robin 轮询(默认在非 Windows 上)
cluster.schedulingPolicy = cluster.SCHED_RR;
console.log('负载均衡策略:', cluster.schedulingPolicy === cluster.SCHED_RR ? 'Round Robin' : 'None');
for (let i = 0; i < os.cpus().length; i++) {
cluster.fork();
}
} else {
const http = require('http');
// 模拟不同处理时间的请求
http.createServer(async (req, res) => {
const delay = Math.random() * 1000;
await sleep(delay);
res.writeHead(200);
res.end(JSON.stringify({
worker: process.pid,
delay: Math.round(delay)
}));
}).listen(8000);
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
3.4 优雅关闭与零停机部署
const cluster = require('cluster');
const http = require('http');
const os = require('os');
if (cluster.isMaster) {
const workers = new Map();
// 创建工作进程
function createWorker() {
const worker = cluster.fork();
workers.set(worker.id, worker);
worker.on('exit', (code, signal) => {
workers.delete(worker.id);
// 如果不是主动关闭,则重启
if (signal !== 'SIGTERM') {
console.log(`工作进程 ${worker.process.pid} 异常退出,正在重启...`);
createWorker();
}
});
return worker;
}
// 初始创建工作进程
for (let i = 0; i < os.cpus().length; i++) {
createWorker();
}
// 优雅重启所有工作进程
function gracefulReload() {
console.log('开始优雅重启...');
const workerIds = Array.from(workers.keys());
let index = 0;
function restartNext() {
if (index >= workerIds.length) {
console.log('所有工作进程已重启');
return;
}
const workerId = workerIds[index++];
const worker = workers.get(workerId);
if (worker) {
console.log(`正在重启工作进程 ${worker.process.pid}...`);
// 发送断开连接信号
worker.disconnect();
// 超时强制终止
const timeout = setTimeout(() => {
console.log(`工作进程 ${worker.process.pid} 超时,强制终止`);
worker.kill('SIGTERM');
}, 5000);
worker.on('disconnect', () => {
clearTimeout(timeout);
console.log(`工作进程 ${worker.process.pid} 已断开连接`);
});
// 创建新工作进程
setTimeout(() => {
createWorker();
restartNext();
}, 1000);
}
}
restartNext();
}
// 监听信号进行优雅重启
process.on('SIGUSR2', gracefulReload);
// 优雅关闭
process.on('SIGTERM', () => {
console.log('主进程收到 SIGTERM,开始优雅关闭...');
workers.forEach(worker => {
worker.send({ cmd: 'shutdown' });
worker.disconnect();
});
// 超时后强制退出
setTimeout(() => {
console.log('强制退出');
process.exit(1);
}, 10000);
});
} else {
let server;
let isShuttingDown = false;
// 创建服务器
server = http.createServer((req, res) => {
if (isShuttingDown) {
res.writeHead(503, { 'Connection': 'close' });
res.end('Server is shutting down');
return;
}
// 模拟请求处理
res.writeHead(200);
res.end(`Response from worker ${process.pid}`);
});
server.listen(8000, () => {
console.log(`Worker ${process.pid} 开始监听`);
});
// 处理来自主进程的消息
process.on('message', (msg) => {
if (msg.cmd === 'shutdown') {
console.log(`Worker ${process.pid} 收到关闭信号`);
isShuttingDown = true;
// 关闭服务器,停止接受新连接
server.close(() => {
console.log(`Worker ${process.pid} 服务器已关闭`);
// 等待现有连接处理完成
setTimeout(() => {
process.exit(0);
}, 2000);
});
}
});
// 处理未捕获的异常
process.on('uncaughtException', (err) => {
console.error(`Worker ${process.pid} 未捕获异常:`, err);
// 优雅关闭
server.close(() => {
process.exit(1);
});
// 超时强制退出
setTimeout(() => {
process.exit(1);
}, 5000);
});
}
4. 深入理解
4.1 Cluster 底层原理
const cluster = require('cluster');
const net = require('net');
// Cluster 底层使用 child_process.fork() 创建子进程
// 并通过 IPC 通道进行通信
if (cluster.isMaster) {
console.log('Master 进程分析:');
console.log('- 负责创建 Worker 进程');
console.log('- 监听 Worker 事件(online, listening, disconnect, exit)');
console.log('- 处理 Worker 间负载均衡');
// 查看 cluster 模块内部实现
console.log('Worker 列表:', cluster.workers);
const worker = cluster.fork();
// 向 Worker 发送消息
worker.send({ hello: 'worker' });
// 接收 Worker 消息
worker.on('message', (msg) => {
console.log('收到 Worker 消息:', msg);
});
// 断开 Worker 连接
setTimeout(() => {
worker.disconnect();
}, 5000);
} else {
console.log(`Worker ${process.pid} 分析:`);
console.log('- 独立的 Node.js 进程');
console.log('- 有自己的 V8 实例和事件循环');
console.log('- 内存不共享');
console.log('- 通过 IPC 与 Master 通信');
// Worker 接收 Master 消息
process.on('message', (msg) => {
console.log('收到 Master 消息:', msg);
// 回复 Master
process.send({ reply: 'hello master', pid: process.pid });
});
// 创建服务器
const server = net.createServer((socket) => {
socket.end(`Handled by worker ${process.pid}\n`);
});
server.listen(8000, () => {
console.log(`Worker ${process.pid} 监听端口 8000`);
});
}
4.2 进程间内存共享方案
const cluster = require('cluster');
/**
* 由于 Worker 间内存不共享,需要使用外部存储
* 方案:Redis、共享内存、数据库
*/
// 方案 1: 使用 Redis 共享数据
const Redis = require('ioredis');
class SharedState {
constructor() {
this.redis = new Redis();
this.localCache = new Map();
}
async get(key) {
// 先查本地缓存
if (this.localCache.has(key)) {
return this.localCache.get(key);
}
// 查 Redis
const value = await this.redis.get(key);
if (value) {
this.localCache.set(key, value);
}
return value;
}
async set(key, value, ttl = 3600) {
this.localCache.set(key, value);
await this.redis.setex(key, ttl, value);
}
// 发布订阅模式实现跨进程通知
subscribe(channel, callback) {
const subscriber = new Redis();
subscriber.subscribe(channel);
subscriber.on('message', (ch, message) => {
callback(JSON.parse(message));
});
}
publish(channel, message) {
this.redis.publish(channel, JSON.stringify(message));
}
}
// 方案 2: 使用共享内存(仅限于同一台机器)
const SharedArrayBuffer = require('worker_threads').SharedArrayBuffer;
// 注意:SharedArrayBuffer 主要用于 Worker Threads,
// Cluster 进程间需要使用其他 IPC 机制
// 方案 3: 使用数据库
const sharedDB = {
async getCounter(name) {
// 使用数据库事务保证原子性
const result = await db.query(
'UPDATE counters SET value = value + 1 WHERE name = ? RETURNING value',
[name]
);
return result.rows[0].value;
}
};
4.3 与 Worker Threads 对比
/**
* Cluster vs Worker Threads
*
* Cluster:
* - 多个独立进程
* - 适合 I/O 密集型任务
* - 进程崩溃不影响其他进程
* - 内存不共享
* - 适合 Web 服务器
*
* Worker Threads:
* - 同一进程内的多个线程
* - 适合 CPU 密集型任务
* - 可以共享内存(SharedArrayBuffer)
* - 普通 JS 未捕获异常终止该 worker,并向父线程发出 error
* - 父线程未处理 error 或原生级崩溃仍可能影响整个进程
* - 适合复杂计算
*/
// Worker Threads 示例
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
if (isMainThread) {
// 主线程
console.log('使用 Worker Threads 进行 CPU 密集型计算');
const worker = new Worker(__filename, {
workerData: { n: 40 } // 传递数据给 Worker
});
worker.on('message', (result) => {
console.log('计算结果:', result);
});
worker.on('error', console.error);
} else {
// Worker 线程
function fibonacci(n) {
return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
}
const result = fibonacci(workerData.n);
parentPort.postMessage(result);
}
// Cluster + Worker Threads 结合使用
// Cluster 处理 HTTP 请求,Worker Threads 处理 CPU 密集型任务
const cluster = require('cluster');
const { Worker } = require('worker_threads');
if (cluster.isMaster) {
// Cluster Master 创建多个 Worker 进程
for (let i = 0; i < require('os').cpus().length; i++) {
cluster.fork();
}
} else {
// Cluster Worker 进程
const http = require('http');
http.createServer((req, res) => {
if (req.url === '/compute') {
// 使用 Worker Thread 进行计算
const worker = new Worker('./compute-worker.js', {
workerData: { n: 35 }
});
worker.on('message', (result) => {
res.end(`Result: ${result}`);
});
worker.on('error', (err) => {
res.statusCode = 500;
res.end(`Error: ${err.message}`);
});
} else {
res.end(`Hello from process ${process.pid}`);
}
}).listen(8000);
}
4.4 多个线程如何按顺序输出数字?
先明确题意:创建 workerCount 个真实 Worker Threads,由它们轮流处理 1..limit,最终只在父线程输出,要求数字恰好出现一次且严格递增。下面选择消息接力,每次只向一个 worker 派发一个数字;收到并输出后才派发下一个。Promise 只等待跨线程消息,线程本身由 Worker 创建。
const { Worker } = require('node:worker_threads');
async function printInWorkerOrder(limit, workerCount = 3, emit = value => console.log(value), timeoutMs = 5000) {
if (!Number.isSafeInteger(limit) || limit < 0 ||
!Number.isSafeInteger(workerCount) || workerCount < 1 ||
typeof emit !== 'function' || !Number.isFinite(timeoutMs) ||
timeoutMs <= 0 || timeoutMs > 2147483647) {
throw new TypeError('Invalid worker options');
}
if (limit === 0) return;
const workers = [];
let timer;
let stopped = false;
try {
await new Promise((resolve, reject) => {
let next = 1;
let active;
const fail = error => {
if (stopped) return;
stopped = true;
reject(error);
};
const dispatch = () => {
active = workers[(next - 1) % workers.length];
timer = setTimeout(() => fail(new Error('Worker reply timed out')), timeoutMs);
active.postMessage(next);
};
for (let index = 0; index < Math.min(workerCount, limit); index++) {
const worker = new Worker(`
const { parentPort, threadId } = require('node:worker_threads');
parentPort.on('message', value => parentPort.postMessage({ value, threadId }));
`, { eval: true });
workers.push(worker);
worker.on('error', fail);
worker.on('exit', code => {
if (!stopped) fail(new Error('Worker exited before completion: ' + code));
});
worker.on('message', reply => {
if (stopped) return;
clearTimeout(timer);
try {
if (worker !== active || reply?.value !== next || reply.threadId !== worker.threadId) {
throw new Error('Unexpected worker reply');
}
emit(reply.value, reply.threadId);
if (next === limit) {
stopped = true;
resolve();
} else {
next++;
dispatch();
}
} catch (error) {
fail(error);
}
});
}
dispatch();
});
} finally {
stopped = true;
clearTimeout(timer);
await Promise.allSettled(workers.map(worker => worker.terminate()));
}
}
printInWorkerOrder(9).catch(console.error);
// 父线程依次输出 1、2、3、4、5、6、7、8、9。
保持顺序的不变量是“最多一个在途数字”:父线程是唯一输出者,只有验证当前序号的回复并同步调用 emit 后才继续。emit 的约定是同步函数,若实际写入目标是异步流,还需等待写入完成或处理背压,不能直接传入异步回调假设它已完成。worker 的普通未捕获异常通过 error 通知父线程;父线程应处理它,并不意味着任一普通 JS 异常都会直接杀死整个进程。
正常结束、输出函数抛错、worker 异常退出或回复超时都会清理全部已创建线程。这个例子没有并行加速收益,线程启动和消息通信反而有成本;它回答的是顺序协调问题。若题目要求并行计算,可同时派发有限数量的带序号任务,在父线程缓存乱序结果,只输出从期望序号开始的连续结果,并为缓存、超时和取消设上限。若要求每个线程自行向共享终端写入,则需另定义共享状态、锁和实际写入完成的规则,不能用各线程的 console.log 调用时间来证明最终顺序。
5. 最佳实践
5.1 生产级 Cluster 配置
const cluster = require('cluster');
const os = require('os');
const http = require('http');
const CONFIG = {
// 工作进程数,通常等于 CPU 核心数
workers: process.env.WORKERS || os.cpus().length,
// 优雅关闭超时(毫秒)
shutdownTimeout: 10000,
// 进程重启间隔(毫秒)
restartDelay: 1000,
// 最大重启次数(防止无限重启)
maxRestarts: 5
};
if (cluster.isMaster) {
console.log(`Master ${process.pid} 启动`);
const restartCount = new Map();
// 创建工作进程
for (let i = 0; i < CONFIG.workers; i++) {
createWorker();
}
function createWorker() {
const worker = cluster.fork();
restartCount.set(worker.id, 0);
worker.on('exit', (code, signal) => {
const count = restartCount.get(worker.id) || 0;
if (signal === 'SIGTERM' || code === 0) {
console.log(`Worker ${worker.process.pid} 正常退出`);
return;
}
if (count >= CONFIG.maxRestarts) {
console.error(`Worker ${worker.process.pid} 重启次数过多,放弃重启`);
return;
}
console.log(`Worker ${worker.process.pid} 退出,准备重启 (${count + 1}/${CONFIG.maxRestarts})`);
setTimeout(() => {
const newWorker = createWorker();
restartCount.set(newWorker.id, count + 1);
}, CONFIG.restartDelay);
});
return worker;
}
// 优雅重启
process.on('SIGUSR2', () => {
console.log('收到 SIGUSR2,开始优雅重启...');
const workers = Object.values(cluster.workers);
let index = 0;
function restartNext() {
if (index >= workers.length) return;
const worker = workers[index++];
worker.send({ cmd: 'graceful-shutdown' });
worker.disconnect();
const timeout = setTimeout(() => {
worker.kill('SIGTERM');
}, CONFIG.shutdownTimeout);
worker.on('disconnect', () => {
clearTimeout(timeout);
createWorker();
setTimeout(restartNext, CONFIG.restartDelay);
});
}
restartNext();
});
// 优雅关闭
process.on('SIGTERM', () => {
console.log('收到 SIGTERM,开始优雅关闭...');
Object.values(cluster.workers).forEach(worker => {
worker.send({ cmd: 'graceful-shutdown' });
worker.disconnect();
});
setTimeout(() => {
console.log('强制退出');
process.exit(1);
}, CONFIG.shutdownTimeout);
});
} else {
// Worker 进程
let server;
let isShuttingDown = false;
// 健康检查端点
const healthChecks = {
'/health': () => ({ status: 'ok', pid: process.pid }),
'/ready': () => isShuttingDown ? null : { status: 'ready' }
};
server = http.createServer((req, res) => {
// 健康检查
if (healthChecks[req.url]) {
const result = healthChecks[req.url]();
if (result) {
res.writeHead(200);
res.end(JSON.stringify(result));
} else {
res.writeHead(503);
res.end(JSON.stringify({ status: 'not ready' }));
}
return;
}
if (isShuttingDown) {
res.writeHead(503, { 'Connection': 'close' });
res.end('Server is shutting down');
return;
}
// 业务逻辑
res.writeHead(200);
res.end(`Hello from ${process.pid}`);
});
server.listen(8000, () => {
console.log(`Worker ${process.pid} 启动,监听端口 8000`);
});
// 处理关闭信号
process.on('message', (msg) => {
if (msg.cmd === 'graceful-shutdown') {
isShuttingDown = true;
server.close(() => {
console.log(`Worker ${process.pid} 服务器已关闭`);
process.exit(0);
});
setTimeout(() => {
console.log(`Worker ${process.pid} 关闭超时`);
process.exit(1);
}, CONFIG.shutdownTimeout);
}
});
// 错误处理
process.on('uncaughtException', (err) => {
console.error(`Worker ${process.pid} 未捕获异常:`, err);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error(`Worker ${process.pid} 未处理 Promise 拒绝:`, reason);
});
}
5.2 PM2 集群模式原理
/**
* PM2 是一个进程管理器,其集群模式基于 Node.js Cluster 模块
* 但提供了更多功能:
* - 自动负载均衡
* - 零停机重载
* - 日志管理
* - 监控
* - 进程守护
*/
// ecosystem.config.js - PM2 配置文件
module.exports = {
apps: [{
name: 'my-app',
script: './app.js',
// 集群模式
exec_mode: 'cluster',
// 实例数(0 = 使用所有 CPU)
instances: 0,
// 自动重启
autorestart: true,
// 内存限制(超过则重启)
max_memory_restart: '1G',
// 日志
log_file: './logs/combined.log',
out_file: './logs/out.log',
error_file: './logs/error.log',
// 环境变量
env: {
NODE_ENV: 'development'
},
env_production: {
NODE_ENV: 'production'
},
// 监听文件变化(开发模式)
watch: false,
// 忽略的文件
ignore_watch: ['node_modules', 'logs'],
// 优雅关闭超时
kill_timeout: 5000,
// 监听端口(PM2 会自动分配)
// 应用内使用 process.env.PORT
// 合并日志(所有实例写入同一文件)
merge_logs: true,
// 日志日期格式
log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
}]
};
// PM2 常用命令
// pm2 start ecosystem.config.js
// pm2 reload my-app # 零停机重载
// pm2 scale my-app +2 # 增加 2 个实例
// pm2 scale my-app 4 # 设置实例数为 4
// pm2 monit # 监控
// pm2 logs # 查看日志
5.3 状态外化与 Session 共享
const cluster = require('cluster');
const session = require('koa-session');
const RedisStore = require('koa-redis');
/**
* 在 Cluster 模式下,Session 必须存储在外部存储中
* 不能依赖进程内存
*/
const Koa = require('koa');
const app = new Koa();
// 使用 Redis 存储 Session
app.keys = ['your-secret-key'];
app.use(session({
store: new RedisStore({
host: 'localhost',
port: 6379
}),
key: 'koa:sess',
maxAge: 86400000, // 1天
httpOnly: true,
secure: false
}, app));
// 其他需要共享的状态也使用 Redis
const redis = require('redis').createClient();
// 共享计数器
async function incrementCounter(name) {
return await redis.incr(`counter:${name}`);
}
// 共享配置
async function getConfig(key) {
return await redis.get(`config:${key}`);
}
// 分布式锁
async function acquireLock(lockName, timeout = 10000) {
const token = Date.now().toString();
const acquired = await redis.set(
`lock:${lockName}`,
token,
'PX',
timeout,
'NX'
);
return acquired ? token : null;
}
async function releaseLock(lockName, token) {
const current = await redis.get(`lock:${lockName}`);
if (current === token) {
await redis.del(`lock:${lockName}`);
}
}
6. 面试要点
-
为什么需要 Cluster 模块(知识点:Node.js 单线程模型)
- Node.js 单线程只能利用一个 CPU 核心
- 多核服务器需要创建多个进程来充分利用资源
- 提高应用吞吐量和可用性
-
Master 和 Worker 的职责分工(知识点:进程间关系)
- Master:进程管理、负载均衡、故障恢复、不处理业务请求
- Worker:处理 HTTP 请求、执行业务逻辑
- Master 和 Worker 通过 IPC 通信
-
负载均衡策略(知识点:负载均衡策略)
- Round Robin(轮询):默认策略,依次分配请求
- None:由操作系统决定(Windows 默认)
- 可以通过
cluster.schedulingPolicy设置
-
进程间通信方式(知识点:进程间通信(IPC))
worker.send()/process.send():发送消息worker.on('message')/process.on('message'):接收消息- 由于内存不共享,需要通过 IPC 或外部存储(Redis)共享数据
-
Cluster 与 Worker Threads 的区别(知识点:与 Worker Threads 对比)
- Cluster:多进程,适合 I/O 密集型,进程崩溃隔离
- Worker Threads:多线程,适合 CPU 密集型,可共享内存
- 可以结合使用:Cluster 处理 HTTP,Worker Threads 处理计算
-
PM2 集群模式的优势(知识点:PM2 集群模式原理)
- 基于 Cluster 模块,但提供更多功能
- 零停机重载、自动重启、内存限制
- 日志管理、监控、进程守护
- 简化 Cluster 的使用
-
生产环境注意事项(知识点:生产级 Cluster 配置,知识点:状态外化与 Session 共享)
- Session 和状态必须外化到 Redis 等存储
- 实现优雅关闭,避免请求中断
- 设置进程重启限制,防止无限重启
- 实现健康检查端点
- 处理未捕获异常和 Promise 拒绝