> Как писать приложение для корректной работы в кластерном режиме с несколькими воркерами (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')для автоматического восстановления.
На практике
- Выберите stateless-архитектуру: каждый запрос должен содержать всю информацию для обработки (токен, идентификатор сессии). Это позволяет балансировать запросы между любыми воркерами без потери данных.
- Используйте Redis для кэша и сессий: это единое хранилище, доступное всем воркерам. Настройте TTL для автоматической очистки.
- Настройте graceful shutdown: в обработчике сигнала
SIGTERMзакройте HTTP-сервер (вызовитеserver.close()), дождитесь завершения текущих запросов (через счётчик активных запросов), затем завершите процесс. - Используйте PM2 или Kubernetes: эти инструменты управляют кластеризацией, мониторингом и рестартом воркеров. PM2 поддерживает режим cluster с автоматическим перезапуском.
- Избегайте синхронизации через файлы: если нужно записывать логи, используйте библиотеки с поддержкой многопроцессной записи (например,
winstonс транспортом в базу данных или через сокет). - Настройте лимиты: установите
max-old-space-sizeдля каждого воркера, чтобы избежать утечек памяти.
Пример кода
JAVASCRIPTconst cluster = require('cluster');const http = require('http');const numCPUs = require('os').cpus().length;if (cluster.isMaster) {console.log(`Master ${process.pid} is running`);// Fork workersfor (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 processconst server = http.createServer((req, res) => {// Simulate async worksetTimeout(() => {res.writeHead(200);res.end('Hello from worker ' + process.pid);}, 100);});// Graceful shutdownprocess.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 в воркере.
> Похожие задачи по JavaScript
Что такое нормализация и денормализация баз данных
Какие тактические шаблоны DDD вы знаете и применяли
Как выглядела структура проекта и где была сосредоточена логика
Интересовались ли вы системным дизайном
> Похожие задачи по backend
Опыт работы с Kafka и другими очередями сообщений
В чем преимущество PostgreSQL перед MongoDB
Что такое Redis Cluster
Как работать со сложными запросами в PostgreSQL
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью