Real-Time Backend

Первое real-time приложение

2008 год. Guillermo Rauch, 19 лет, один уикенд - и real-time web перестаёт требовать Flash. Socket.io показывает чат на 100 пользователей прямо в браузере с задержкой 50ms. Индустрия никогда не будет прежней.

  • **Slack** использует WebSocket + custom protocol для доставки сообщений 16M+ одновременных пользователей - те же принципы rooms и presence
  • **Figma** строит collaborative редактор поверх WebSocket с operational transforms - основа та же: connection state reconciliation при reconnect
  • **Discord** обрабатывает 850M+ сообщений в день через Gateway API (WebSocket) с presence для 500M+ пользователей - rooms это guild channels
  • **GitHub Copilot** стримит токены через SSE/WebSocket - streaming как частный случай broadcasting одному клиенту

Socket.io: почему не просто WebSocket

2008 год. Guillermo Rauch - 19 лет, живёт в Буэнос-Айресе. За один уикенд пишет Socket.io и показывает живой чат на 100 одновременных пользователей с задержкой меньше 50ms прямо в браузере. Без Flash. Без Java Applets. Просто браузер и JavaScript. Аудитория не верит глазам.

До Socket.io real-time web требовал либо Flash-плагина (Adobe, проприетарный, дырявый), либо Java Applets (Oracle, тяжёлые, ненавидимые пользователями), либо хитрых long-polling трюков которые ломались на каждом втором корпоративном прокси. Socket.io убил всё это разом.

Socket.io - это не просто WebSocket. Это протокол поверх WebSocket (или HTTP long-polling как fallback) с автоматическим переподключением, комнатами, неймспейсами и acknowledgement-механизмом. Engine.io под капотом выбирает лучший транспорт автоматически.

Архитектура Socket.io состоит из двух слоёв. Engine.io отвечает за транспорт - сначала пробует HTTP long-polling, потом апгрейдит до WebSocket если браузер поддерживает. Socket.io Protocol сидит сверху и добавляет мультиплексирование (неймспейсы), rooms и надёжную доставку событий.

Каждое соединение - это объект `socket` с уникальным `socket.id`. Сервер ведёт в памяти Map всех активных сокетов. Это одновременно главная сила и главная засада Socket.io: при рестарте сервера весь этот Map исчезает. Клиенты переподключаются, но сервер о них ничего не знает.

Socket.io v3+ и v2 несовместимы на уровне протокола. Клиент v2 не подключится к серверу v3. Версии должны совпадать. В монорепо - фиксировать точную версию в обоих package.json.

Engine.io начинает соединение с HTTP long-polling и только потом апгрейдит до WebSocket. Зачем такой порядок, а не сразу WebSocket?

Rooms и Namespaces: мультиплексирование на практике

Namespace - это виртуальный сокет-сервер на одном TCP-соединении. Офисный билдинг: одна входная дверь (WebSocket соединение), но внутри разные этажи (неймспейсы) с разными правилами доступа. `/chat`, `/admin`, `/notifications` - три разных неймспейса, три изолированных пространства событий, одно соединение.

Room - это группа внутри неймспейса. Если неймспейсы - этажи, то комнаты - переговорки. Один сокет может быть одновременно в нескольких комнатах. `socket.join('room-42')` добавляет сокет в комнату. `socket.leave('room-42')` убирает. При дисконнекте Socket.io автоматически чистит все комнаты.

Socket.io хранит rooms в памяти сервера. При горизонтальном масштабировании на несколько нод - rooms существуют только на своей ноде. Пользователь на Node A не получит событие из room на Node B. Решение - Socket.io Redis Adapter: rooms синхронизируются через Redis pub/sub.

Это именно то место где большинство чат-приложений ломаются при росте. Один сервер - работает. Два сервера за балансировщиком - половина пользователей не видит сообщения. Redis Adapter решает проблему в 3 строки кода, но его нужно знать заранее, не когда уже горит.

Пользователь подключён к `/chat` namespace и находится в room `general`. Какой вызов отправит сообщение всем в room `general` КРОМЕ самого пользователя?

Broadcasting, presence indicators и connection state reconciliation

Зелёный кружок рядом с аватаром в Slack стоит дорого. Не метафорически - буквально. Slack обрабатывает 16 миллионов одновременных пользователей. Каждый из них периодически шлёт heartbeat со своим online-статусом. Каждый получает обновления о статусе других. Это O(N^2) трафик при наивной реализации. Slack решает его через умный fan-out и topic partitioning.

В Socket.io presence реализуется через комбинацию `connect`/`disconnect` событий и явного управления состоянием. Нельзя просто слушать `connect` - при нестабильном интернете пользователь может переподключиться 10 раз за минуту, генерируя 20 событий. Нужен debounce на уровне бизнес-логики.

Connection state reconciliation - самая важная часть, которую забывают 90% туториалов. Что происходит когда пользователь на 30 секунд потерял интернет и переподключился? Socket.io автоматически reconnect - отлично. Но пока он был offline, в чат пришло 15 сообщений. Они потеряны навсегда если не сделать явную синхронизацию.

Socket.io v4.5+ добавил встроенный `connection state recovery` через `io({ auth: { sessionId } })`. Сервер буферизует события пока клиент переподключается и отдаёт их при reconnect. Но максимальный буфер ограничен - для серьёзных приложений всё равно нужна своя reconciliation логика через БД.

Итог: Socket.io убрал весь cross-browser pain 2008 года. Но добавил свой слой абстракции с которым нужно уметь работать. Rooms и namespaces - мультиплексирование бесплатно. Presence - требует явного debounce. Reconnection reconciliation - требует хранения event ID на клиенте и replay-логики на сервере. Это не сложно - это просто нужно знать.

Socket.io - это просто удобная обёртка над WebSocket, и все данные хранятся в соединении

Socket.io - это отдельный протокол (Engine.io + Socket.io Protocol) поверх WS или HTTP. Данные живут в памяти сервера или в Redis при масштабировании - соединение не хранит state

WebSocket - это просто байтовый канал. Вся логика rooms, namespaces, acknowledgements, reconnection - это Socket.io Protocol, не WebSocket. При рестарте сервера весь in-memory state пропадает. Персистентность - задача приложения.

Пользователь потерял интернет на 20 секунд, Socket.io автоматически переподключился. Какой механизм гарантирует что он увидит сообщения, пришедшие пока он был offline?

Ключевые идеи

  • **Socket.io = Engine.io + Socket.io Protocol**: транспорт (WS/polling fallback) отделён от мультиплексирования (namespaces/rooms). Это осознанная архитектура, не случайность
  • **Rooms живут в памяти одной ноды**: при горизонтальном масштабировании нужен Redis Adapter - это не опциональная оптимизация, это обязательное требование
  • **Presence требует debounce**: mobile сети переключаются между WiFi и 4G, генерируя множество reconnect. 5 секунд grace period на disconnect - минимум
  • **Connection reconciliation - ответственность приложения**: lastEventId на клиенте + replay из БД при reconnect. Socket.io v4.5 connection recovery как дополнение, не замена

Связанные темы

Real-time архитектура чата пересекается с несколькими фундаментальными областями:

  • Event Loop Node.js — Неблокирующая обработка тысяч сокетов одним потоком
  • WebSocket Protocol — Базовый транспорт под Socket.io Engine.io слоем
  • Сравнение подходов (SSE/WS/Polling) — Контекст для выбора Socket.io vs других транспортов
  • Redis pub/sub — Синхронизация rooms между нодами Socket.io кластера

Вопросы для размышления

  • Slack показывает typing indicator когда кто-то набирает текст. Как бы была устроена такая фича на Socket.io - какие events, с какой частотой, и как избежать flood от 50 одновременно печатающих?
  • Если Socket.io сервер перезапускается, все in-memory rooms пропадают. Что произойдёт с клиентами - они переподключатся автоматически? Как сервер узнает в какие rooms их вернуть?
  • Presence indicator показывает пользователя online если у него открыто несколько вкладок. Что должно произойти когда он закрывает одну вкладку из трёх - статус должен поменяться?

Связанные уроки

  • rt-04 — WebSocket RFC 6455 - основа под Socket.io
  • rt-05 — Анатомия фреймов нужна чтобы понимать overhead
  • node-01-event-loop — Event loop - почему Node.js идеален для Socket.io
  • rt-06 — Сравнение подходов: когда выбирать Socket.io vs SSE
  • net-06-ip-intro — IP и порты важны для понимания namespace routing
  • net-36-websocket
Первое real-time приложение

0

1

Войти