Node.js Cluster:将你的单核应用变身多核性能猛兽!

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 核心,而不仅仅是一个

  • 主进程负责管理,工作进程处理请求

  • 负载均衡自动进行

  • 始终处理优雅关闭

  • 监控集群健康状况

  • 调整工作进程大小时考虑工作负载类型

从一个简单的集群设置开始,然后随着需求的增长逐渐添加监控、自动扩展和专业化工作进程。你未来的自己(和你的服务器账单)会感谢你的!

内容翻译自:https://dev.to/imkrunalkanojiya/nodejs-cluster-turn-your-single-core-app-into-a-multi-core-beast-39pa

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值