Sticky session — IP affinity
Закрепление клиента за конкретным воркером/сервером по IP — нужно для stateful-протоколов (WebSocket, in-memory sessions).
Что это
При cluster/horizontal scaling каждое новое TCP-соединение может попасть на любой воркер. Это проблема, когда состояние сессии хранится в памяти конкретного процесса (WebSocket-соединение, кэш авторизации, локальные временные данные).
Sticky session = всегда направлять клиента на один и тот же worker. Самый простой способ — IP affinity: worker_id = hash(clientIP) % numWorkers.
API / Пример
const cluster = require('cluster');
const net = require('net');
const os = require('os');
const numWorkers = os.cpus.length;
if (cluster.isPrimary) {
// Поднимаем воркеры — каждый слушает свой порт
const workers = ;
for (let i = 0; i < numWorkers; i++) workers.push(cluster.fork({ WORKER_ID: i }));
// Один TCP-листенер на мастере, который раздаёт сокеты воркерам по IP
const server = net.createServer({ pauseOnConnect: true }, (socket) => {
const ip = socket.remoteAddress;
const id = ipToInt(ip) % numWorkers;
workers[id].send('socket', socket);
});
server.listen(3000);
} else {
process.on('message', (msg, socket) => {
if (msg === 'socket') {
// обработать socket в этом воркере
socket.resume;
}
});
}
function ipToInt(ip) {
return ip.split('.').reduce((acc, oct) => (acc << 8) + Number(oct), 0) >>> 0;
}
Альтернативы
- Клиентская балансировка — клиент сам выбирает воркер по своему хешу или после авторизации получает порт.
- Балансер L4 (nginx, HAProxy) с IP hash —
ip_hashдиректива в upstream. - Sticky cookie — балансер ставит cookie с ID воркера; работает только для HTTP.
- Redis adapter для WebSocket — Socket.io синхронизирует события через Redis, sticky не нужен (но всё равно желателен для меньшей задержки).
Производительность / Подводные камни
- Неравномерная нагрузка — если по подсети одинаковый префикс, все попадут на один воркер.
- NAT — много пользователей за одним IP уйдут на один воркер.
- Migrating session — при падении воркера нужно куда-то переехать; готовиться к этому через external storage.
- На больших кластерах cluster-mode не масштабируется — мастер 100% CPU. Переходить на L4/L7 балансер.
🎓 Источники
-
🎓 HTTP сервер на Node.js (routing, cluster, IP sticky) · 2018-10-17
- Тезисы:
workerId = ipToInt(socket.remoteAddress) % numCPUs; в воркереserver.listen(null)чтобы симулировать прослушку без bind; в воркер сокет передаётся черезworker.send('socket', socket); роутинг таблицей быстрее middleware-цепочки. - Цитата: «Само приложение должно сгенерировать рандомное число. Или после авторизации вам выдадут номер порта, или можно в HTTP-заголовках прислать список портов, между которыми клиент сам балансируется.»
- Тезисы:
-
🎓 Высоконагруженные распределённые приложения на Node.js · 2018-10-30
- Тезисы: серверная балансировка не нужна — балансировку выносить на клиент; хеш от IP против DDoS; консолидация юзеров на сервере по взаимодействию (chat-rooms на одном инстансе).