> Как писать приложение для корректной работы в кластерном режиме с несколькими воркерами (JavaScript)

Уровень: senior · Роль: backend · Язык: JavaScript · Категория: Технические вопросы

Компании: QueenInteractiveGamesLtd

Стек: Node.js, JavaScript

> Пример ответа

Короткий ответ

Кластерный режим в Node.js запускает несколько воркеров (процессов) на разных CPU. Для корректной работы нужно: использовать разделяемое хранилище (Redis, база данных) для сессий и кэша, избегать синхронизации через файловую систему, правильно обрабатывать сигналы завершения, настраивать sticky sessions или stateless-архитектуру, и использовать IPC или брокеры сообщений для межпроцессного взаимодействия.

Подробное объяснение

В Node.js кластерный режим создаётся через модуль cluster, где мастер-процесс распределяет входящие соединения между воркерами. Основные проблемы при работе с несколькими воркерами:

  • Состояние в памяти: каждый воркер имеет свою изолированную память. Данные, хранящиеся в глобальных переменных или кэше воркера, не видны другим. Решение - внешнее хранилище (Redis, Memcached, база данных).
  • Сессии: если сессии хранятся в памяти воркера, пользователь может потерять сессию при переключении на другой воркер. Используйте сессионное хранилище на Redis или sticky sessions (но это снижает отказоустойчивость).
  • Файловая система: одновременная запись в один файл из нескольких воркеров приводит к гонке данных. Используйте блокировки (file locking) или базу данных.
  • Graceful shutdown: при завершении воркера нужно корректно закрыть соединения (HTTP, WebSocket, базы данных) и завершить обработку текущих запросов. Используйте сигналы SIGTERM/SIGINT и process.exit() только после завершения всех операций.
  • Межпроцессное взаимодействие: для обмена данными между воркерами используйте IPC (через process.send() и cluster.on('message')) или внешние брокеры (Redis Pub/Sub, RabbitMQ).
  • Health checks: мастер должен отслеживать состояние воркеров и перезапускать упавшие. Используйте cluster.on('exit') для автоматического восстановления.

На практике

  1. Выберите stateless-архитектуру: каждый запрос должен содержать всю информацию для обработки (токен, идентификатор сессии). Это позволяет балансировать запросы между любыми воркерами без потери данных.
  2. Используйте Redis для кэша и сессий: это единое хранилище, доступное всем воркерам. Настройте TTL для автоматической очистки.
  3. Настройте graceful shutdown: в обработчике сигнала SIGTERM закройте HTTP-сервер (вызовите server.close()), дождитесь завершения текущих запросов (через счётчик активных запросов), затем завершите процесс.
  4. Используйте PM2 или Kubernetes: эти инструменты управляют кластеризацией, мониторингом и рестартом воркеров. PM2 поддерживает режим cluster с автоматическим перезапуском.
  5. Избегайте синхронизации через файлы: если нужно записывать логи, используйте библиотеки с поддержкой многопроцессной записи (например, winston с транспортом в базу данных или через сокет).
  6. Настройте лимиты: установите max-old-space-size для каждого воркера, чтобы избежать утечек памяти.

Пример кода

JAVASCRIPT
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
// Fork 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 process
const server = http.createServer((req, res) => {
// Simulate async work
setTimeout(() => {
res.writeHead(200);
res.end('Hello from worker ' + process.pid);
}, 100);
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('Worker received SIGTERM, shutting down gracefully');
server.close(() => {
process.exit(0);
});
});
server.listen(3000);
console.log(`Worker ${process.pid} started`);
}

Как отвечать на собеседовании

Начните с ключевой проблемы - изоляции памяти воркеров. Объясните, что кластеризация решает проблему масштабирования, но требует переосмысления хранения состояния. Приведите примеры: сессии в Redis, кэш в Memcached, graceful shutdown. Если спросят про sticky sessions - объясните trade-off: простота vs отказоустойчивость. Упомяните инструменты (PM2, Kubernetes) и типичные ошибки (гонка данных, утечка соединений). Будьте готовы написать простой кластер на cluster и объяснить, как добавить health check.

Что проверяет интервьюер

  • Понимание архитектуры Node.js (event loop, single-thread, process vs thread).
  • Умение проектировать распределённые системы (shared state, stateless).
  • Знание практик отказоустойчивости (graceful shutdown, restart).
  • Опыт работы с внешними хранилищами (Redis, базы данных).
  • Способность видеть проблемы многопроцессной среды (гонки, блокировки).

Типичные ошибки

  • Хранение сессий в памяти воркера - пользователь теряет сессию при переключении.
  • Игнорирование graceful shutdown - потеря данных при рестарте.
  • Запись в один файл из нескольких воркеров без синхронизации - повреждение данных.
  • Использование глобальных переменных для кэша - неконсистентность.
  • Отсутствие обработки ошибок при fork - мастер может упасть.
  • Неправильная настройка лимитов памяти - OOM в воркере.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью