Масштабирование: несколько серверов и Redis pub/sub

Что ломается, когда серверов становится несколько, и как это чинят.

Pub/sub (издатель-подписчик) — модель обмена, где сообщение публикуют в «канал», а все подписчики на этот канал его получают; используется, чтобы связать несколько серверов.

Проблема нескольких серверов

Один сервер не выдержит миллион соединений — приложение масштабируют горизонтально: ставят несколько серверов за балансировщиком. Но возникает беда: соединения «размазаны». Анна подключена к серверу №1, Иван — к серверу №2. Анна шлёт сообщение в комнату, сервер №1 разошлёт его только своим клиентам — Иван на сервере №2 ничего не получит. Список клиентов у каждого сервера свой.

      Анна            Иван
       |               |
   Сервер 1        Сервер 2
   (знает A)       (знает И)
       \               /
   broadcast от Анны НЕ дойдёт до Ивана

Решение: Redis pub/sub

Серверы связывают общей шиной — обычно Redis. Когда сервер получает сообщение, он не только шлёт своим клиентам, но и публикует его в Redis-канал. Все остальные серверы подписаны на этот канал, получают сообщение и рассылают уже своим клиентам.

   Анна --> Сервер 1 --publish--> [ Redis ] --notify--> Сервер 2 --> Иван
                  |
              своим клиентам                                 

Sticky sessions

Ещё одна тонкость — балансировщик. WebSocket держит постоянное соединение, поэтому все пакеты одного клиента должны идти на тот же сервер, что принял рукопожатие. Это обеспечивают sticky sessions (закрепление клиента за сервером). Без них балансировщик может перекинуть клиента на другой сервер посреди соединения — и канал порвётся.

Как работает под капотом

Redis pub/sub — это не хранилище сообщений, а быстрый «громкоговоритель»: опубликованное доходит только тем, кто подписан сейчас. Поэтому он отвечает за рассылку между серверами, а историю чата (чтобы догрузить сообщения после реконнекта) хранят отдельно — в базе. У Socket.IO для этого есть готовый «адаптер» Redis, который берёт публикацию и подписку на себя. По сути это превращает много серверов в один логический broadcast-домен.

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

  • Масштабировать без общей шины. Клиенты на разных серверах не увидят друг друга — нужен Redis pub/sub или аналог.
  • Забыть sticky sessions. Балансировщик порвёт соединение, перекинув клиента на другой сервер.
  • Полагаться на Redis pub/sub как на хранилище. Он не хранит сообщения; историю держите в базе.

Итоги

  • При нескольких серверах их клиенты изолированы — broadcast не пересекает границу сервера.
  • Серверы связывают шиной pub/sub (обычно Redis): один публикует, остальные рассылают своим.
  • Sticky sessions держат клиента на одном сервере всё соединение.
  • Redis pub/sub не хранит историю — её ведут в базе отдельно.
Проверьте себя
1. Почему при нескольких WebSocket-серверах broadcast «не долетает» до части пользователей?
AИз-за шифрования
BУ каждого сервера свой список клиентов, и он рассылает только своим
CБраузеры не поддерживают несколько серверов
DRedis блокирует сообщения
2. Зачем нужны sticky sessions для WebSocket?
AЧтобы шифровать трафик
BЧтобы все пакеты клиента шли на тот же сервер, что принял рукопожатие
CЧтобы хранить историю сообщений
DЧтобы ускорить рукопожатие
3. Что делает Redis в pub/sub-схеме реалтайма?
AХранит всю историю чата навсегда
BПередаёт сообщения между серверами: один публикует, другие получают
CШифрует WebSocket-канал
DБалансирует нагрузку вместо балансировщика