Cluster: масштабирование

cluster — встроенный модуль Node.js, позволяющий создать несколько дочерних процессов (workers) на основе одного мастер-процесса, каждый из которых слушает один и тот же порт, используя все CPU-ядра.

Зачем нужно

Node.js по умолчанию использует одно ядро CPU. На 8-ядерном сервере 7 ядер простаивают. Cluster создаёт столько воркеров, сколько ядер, и балансирует входящие соединения между ними через операционную систему (round-robin). Это увеличивает throughput пропорционально числу ядер. В production чаще используют pm2 -i max вместо ручного cluster.

Где используется

  • Production-серверы без Docker/Kubernetes (VPS)
  • Когда нужно максимальное использование CPU
  • Под капотом PM2 cluster mode
  • Замена Worker Threads для HTTP-нагрузки (I/O bound)

Основной контент

Базовый пример

const cluster = require('cluster');
const http = require('http');
const os = require('os');

const numCPUs = os.cpus.length;

if (cluster.isPrimary) {
  // Мастер-процесс: форкает воркеры
  console.log(`Primary ${process.pid} is running`);
  console.log(`Forking ${numCPUs} workers`);

  for (let i = 0; i < numCPUs; i++) {
    cluster.fork;
  }

  // Перезапускать воркер при сбое
  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died (${signal || code}). Restarting...`);
    cluster.fork;
  });

  cluster.on('online', (worker) => {
    console.log(`Worker ${worker.process.pid} is online`);
  });
} else {
  // Воркеры: запускают HTTP-сервер на том же порту
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end(`Hello from worker ${process.pid}\n`);
  }).listen(3000);

  console.log(`Worker ${process.pid} started`);
}

С Express

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

if (cluster.isPrimary) {
  const numWorkers = os.cpus.length;
  console.log(`Starting ${numWorkers} workers`);

  for (let i = 0; i < numWorkers; i++) {
    cluster.fork;
  }

  cluster.on('exit', (worker, code, signal) => {
    if (!worker.exitedAfterDisconnect) { // не перезапускать при graceful shutdown
      console.log(`Restarting worker ${worker.process.pid}`);
      cluster.fork;
    }
  });

  // Graceful shutdown
  process.on('SIGTERM', () => {
    for (const id in cluster.workers) {
      cluster.workers[id].disconnect();
    }
  });
} else {
  // Обычный Express-сервер
  const express = require('express');
  const app = express;
  app.use(require('./routes'));
  app.listen(3000, () => console.log(`Worker ${process.pid} listening on 3000`));
}

Коммуникация между мастером и воркерами

if (cluster.isPrimary) {
  const worker = cluster.fork;

  // Мастер отправляет воркеру
  worker.send({ type: 'config', data: { maxConnections: 100 } });

  // Мастер получает от воркера
  worker.on('message', (msg) => {
    console.log(`From worker: ${JSON.stringify(msg)}`);
  });
} else {
  // Воркер получает от мастера
  process.on('message', (msg) => {
    if (msg.type === 'config') {
      console.log('Config received:', msg.data);
    }
  });

  // Воркер отправляет мастеру
  process.send({ type: 'stats', requests: 42 });
}

Cluster vs Worker Threads vs PM2

cluster:
  + Несколько OS процессов (изоляция памяти)
  + Каждый воркер — отдельный V8 (crash-isolated)
  - Нет разделяемой памяти
  - Нужен ручной IPC для общения

Worker Threads:
  + Разделяемая память (SharedArrayBuffer)
  + Один процесс
  - Crash одного убивает другие (нет изоляции)
  + Лучше для CPU-bound задач

PM2 -i max:
  + Тот же cluster под капотом
  + Не нужно писать код
  + Встроенный мониторинг и логи
  + Рекомендуется для production

Частые ошибки

  • Хранить состояние в памяти воркера — разные воркеры имеют разную память; сессии, кеш — в Redis
  • Не обрабатывать событие 'exit' — воркер умер и не перезапустился; сервер теряет ёмкость
  • Использовать cluster вместо PM2 — ручное управление cluster сложнее и хуже поддерживается; в production предпочитать PM2
  • Балансировка не гарантирована — ОС распределяет соединения неравномерно; для точной балансировки нужен nginx upstream

Связанные темы

Ресурсы


🎓 Источник: HTTP сервер на Node.js (routing, cluster, IP sticky)

  • 📅 2018-10-17 · YouTube
  • Тезисы:
    • В кластере http.createServer "реален" только в мастере; в воркерах его симулирует cluster.
    • Балансировка — round-robin: мастер раздаёт соединения по очереди.
    • Process ID в заголовке ответа — простая диагностика того, какой воркер ответил.
    • Альтернатива: child_process с отдельным портом на каждый воркер + клиентская балансировка.
    • Sticky-session по IP: workerId = ipToInt(remoteAddress) % numCPUs.
  • Цитата: «Серверный сокет откроется в мастер-процессе, потом с ним свяжутся несколько воркеров. И когда запросы приходят в мастер, он при помощи RoundRobin раздаёт.»

🎓 Источник: Archive 2018 · Processes, Clustering and Balancing in Node.js

  • 📅 2020-01-18 · YouTube
  • Тезисы:
    • cluster.isMaster / cluster.isWorker (или isPrimary) — два пути выполнения в одном файле.
    • До ~8 ядер cluster эффективен; на 20+ ядрах мастер упирается в 100% CPU.
    • На большом числе ядер — переход на child_process.fork(file) со своим сокетом на воркер.
    • Балансировку выносим на клиент (порт = base + workerId) или в L4-балансер.
    • cluster.fork создаёт IPC-канал между master и worker автоматически.
  • Цитата: «Когда ядер становится много, мастер 100% в CPU, а воркеры там 10-20% и не могут нагрузиться.»

🎓 Источник: Высоконагруженные распределённые приложения на Node.js

  • 📅 2018-10-30 · YouTube
  • Тезисы:
    • Предел одного процесса ноды — ~50 тысяч соединений; на сервер — 500к; на кластер — до 10М.
    • Три метрики высокой нагрузки: connections, RPS, latency — измерять одновременно.
    • HighLoad, HighConnectivity, HighInterconnect — разные оси нагрузки, разные оптимизации.
    • Долгоживущие процессы → нельзя рестартить → graceful migration юзеров между процессами.
    • Балансировку лучше выносить на клиент: хеш от IP, защита от DoS через consistent hashing.

🎓 Источник: Обзор встроенного Node.js API

  • 📅 2018-09-26 · YouTube
  • Тезисы:
    • Cluster — обёртка над child_process с round-robin распределением соединений (на Linux)
    • На Windows распределение OS-зависимое (IOCP)
    • Cluster или child_process — выбор зависит от того, нужны ли разные кодовые базы или одна

🎓 Источник: Многопоточность в Metarhia

  • 📅 2021-10-03 · YouTube · [Marp](../../Documents/TimurShemsedinov/2021-10-03 — 💻 Многопоточность в Metarhia (первый сервер приложений для Node.js с менеджером (BLLSuCYKLNk).md)
  • Тезисы: менеджер потоков поверх cluster + worker_threads, шедулинг задач, balancing