Как писать приложение для корректной работы в кластерном режиме с несколькими воркерами
Что проверяют
Понимаете ли вы, что воркеры это отдельные процессы без общей памяти, и чем это грозит коду, написанному под один процесс. Следом спрашивают про graceful shutdown и про то, нужен ли cluster в Kubernetes.
Как отвечать
Основной процесс (cluster.isPrimary, старое isMaster устарело) вызывает cluster.fork(), воркеры слушают один порт. Соединения по умолчанию раздаёт основной процесс по кругу, на всех платформах, кроме Windows. У каждого воркера своя память и свой event loop.
Отсюда правила:
- никакого состояния в памяти: сессии, кэши, счётчики rate limit, блокировки выносят в Redis или базу, иначе запрос попадёт в воркер, который о пользователе ничего не знает;
- фоновые задачи:
setIntervalили cron внутри приложения запустятся в каждом воркере. Их выносят в отдельный процесс или берут распределённую блокировку; - WebSocket: Socket.IO нужны sticky sessions и адаптер для обмена событиями между воркерами, например на Redis;
- падения: на
uncaughtExceptionпишут лог и завершают воркер, работать дальше в неопределённом состоянии нельзя. Основной процесс по событиюexitподнимает новый воркер с задержкой, чтобы ошибка на старте не устроила бесконечный цикл перезапусков; - graceful shutdown: по
SIGTERMвызватьserver.close(), дождаться текущих запросов, закрыть пул базы и выйти, а по таймауту завершиться принудительно. Простаивающие keep-alive соединения закрываетserver.closeIdleConnections().
В Kubernetes обычно запускают один процесс на контейнер и масштабируют репликами. Внутри контейнера os.cpus().length показывает ядра хоста, а не лимит пода, число воркеров лучше брать из конфигурации. worker_threads решают другую задачу: вынос тяжёлых вычислений, а не приём соединений.
Где спотыкаются
Хранят сессии в памяти процесса и потом ловят «случайные» разлогины.
Используют uncaughtException как способ не падать и продолжают обслуживать запросы.
Запускают по воркеру на каждое ядро хоста в поде с лимитом в одно ядро и получают троттлинг.