WebSockets и SSE
1) Коротко: что и для чего
WebSocket (WS/WSS) — апгрейд HTTP-соединения до полно-дуплексного канала. Подходит для чатов, live-игр, коллаборации, двунаправленной телеметрии.
Server-Sent Events (SSE) — односторонний стрим от сервера к браузеру (MIME `text/event-stream`). Идеален для тикеров, уведомлений, котировок, прогресса задач. Клиент — `EventSource`.
- Нужен ввод от клиента в режиме реального времени (часто и много) → WebSocket.
- Только пуш-обновления с сервера, совместимость и простота важнее → SSE.
2) Сеть и протоколы
2.1 Транспорт и совместимость
WebSocket: стартует как HTTP `GET … Upgrade: websocket` (HTTP/1.1). Для HTTP/2 возможен RFC 8441 (CONNECT + `:protocol = websocket`), поддержка зависит от прокси. Работает поверх TLS (WSS) — обязательно в проде.
SSE: обычный длительный HTTP-ответ (`200 OK`) c потоковой передачей. Прекрасно идет через HTTP/1.1/2/3, совместим с CDN/прокси (если не обрывают длинные коннекты).
2.2 Прокси/балансировщики/CDN
Проверьте: поддержка долгоживущих соединений, идла и таймаутов, sticky-сессий (если состояние на узле).
Для WS: включите `proxy_read_timeout`, `upgrade`-заголовки, пер-соединение лимиты.
Для SSE: убедитесь, что прокси не буферизует ответ (иначе клиент не увидит события вовремя).
3) Модель сообщений и управление потоком
WebSocket: кадры текст или бинарные; есть `ping/pong`, но встроенного backpressure нет — реализуйте на приложении (очереди, окна, drop-policy).
SSE: текстовые события (UTF-8); клиент встроенно умеет reconnect с задержкой; сервер может задавать `retry:`. Есть `id:` и заголовок `Last-Event-ID` для возобновления с нужной позиции.
- Квотируйте исходящие сообщения per-клиент.
- Ограничивайте очередь неприсланных событий; при переполнении — отбрасывать низкоприоритетные/агрегировать.
- Для WS: используйте «скользящее окно» и ACK-схему на уровне приложения.
4) Надежность соединений
4.1 Детекция и keepalive
WS: отправляйте `ping` каждые N секунд; разрыв по таймауту — reconnect с экспоненциальным backoff + джиттер.
SSE: сервер шлет «комментарии» `:\n` как heartbeat, чтобы соединение не считали idle; клиент сам переподключится.
4.2 Восстановление потока
WS: держите offset/sequence сообщений и запрашивайте дельту после reconnect.
SSE: используйте `id:` для каждого события и `Last-Event-ID` в запросе — сервер шлет пропущенные события.
5) Аутентификация и авторизация
JWT Bearer в URL запроса (WS) небезопасен (утечки в логах). Используйте заголовок (через первичный HTTP-хэндшейк) или куки с флагами `Secure`, `HttpOnly`, `SameSite`.
Возможен mTLS (особенно для B2B), а также подписи (HMAC) поверх первоначального запроса.
Для SSE с куками помните про CORS (`Access-Control-Allow-Origin`, `Allow-Credentials`).
Ротация токена: не обрывайте стрим. Передавайте «скоро истечет» → клиент переоткроет соединение с новым токеном.
6) Формат данных и компрессия
WebSocket: включайте permessage-deflate осторожно (CPU); избегайте компрессии уже-сжатых форматов (Proto, Avro). Бинарные payload экономнее JSON.
SSE: это текст; для крупных данных отправляйте ссылку на REST/gRPC ресурс или chunk-файлы; транспортный gzip для SSE — уместен, но следите за буферизацией в прокси/CDN.
7) Масштабирование и фан-аут
7.1 Горизонтальное масштабирование
Держите приложение бездефектным к перезапускам. Состояние соединений — во фронт-слое; данные — из брокера.
Sticky (хэш по session/user) нужен, если есть локальные очереди.
Идеал — stateless фронт: узел лишь мультиплексирует подписки; события приходят из общего pub/sub.
7.2 Pub/Sub и брокеры
Для широкого фан-аута используйте Kafka/NATS/Redis Streams.
Слой «Fanout Gateway» подписывается на топики и пушит клиентам по WS/SSE.
Применяйте ключ маршрутизации (например, `userId`, `matchId`) для балансировки нагрузки между нодами.
8) Производственные конфиги
8.1 NGINX — WebSocket
nginx map $http_upgrade $connection_upgrade { default upgrade; '' close; }
server {
listen 443 ssl http2;
server_name ws. example. com;
location /ws {
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1. 1;
proxy_read_timeout 75s; # increase for long sessions proxy_send_timeout 15s;
proxy_pass http://ws-backend;
}
}
8.2 NGINX — SSE (важно отключить буферизацию)
nginx location /events {
proxy_http_version 1. 1;
proxy_set_header Connection "";
proxy_buffering off; # is critical for proxy_cache off threads;
chunked_transfer_encoding on;
proxy_read_timeout 60m;
proxy_pass http://sse-backend;
}
8.3 Kubernetes (Ingress Annotations, NGINX Ingress)
yaml metadata:
annotations:
nginx. ingress. kubernetes. io/proxy-read-timeout: "3600"
nginx. ingress. kubernetes. io/proxy-send-timeout: "3600"
nginx. ingress. kubernetes. io/enable-websocket: "true"
nginx. ingress. kubernetes. io/proxy-buffering: "off" # для SSE
9) Примеры кода
9.1 Клиент SSE (браузер)
js const es = new EventSource("/events? channel=odds", { withCredentials: true });
es. addEventListener("message", (e) => {
const data = JSON. parse(e. data);
renderOdds(data);
});
es. addEventListener("error", () => {
//EventSource will reconnect itself; can be shown spinner
});
9.2 Сервер SSE (Node.js/Express)
js app. get('/events', (req, res) => {
res. writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
res. write ('retry: 3000\n\n') ;//3s backoff to client
const sub = subscribe(req. query. channel, (event) => {
res. write(`id: ${event. id}\n`);
res. write(`event: message\n`);
res. write(`data: ${JSON. stringify(event. payload)}\n\n`);
});
req. on('close', () => sub. unsubscribe());
});
9.3 Клиент WebSocket (браузер)
js const ws = new WebSocket("wss://ws. example. com/ws");
ws. onopen = () => ws. send(JSON. stringify({ type: "join", room: "chat-1" }));
ws. onmessage = (m) => handle(JSON. parse(m. data));
ws. onclose = () => scheduleReconnect();
10) Безопасность и лимитирование
Rate limiting per-соединение и per-пользователь: ограничивайте частоту входящих сообщений (WS) и скорость исходящего потока (WS/SSE).
Quota по времени жизни соединения и суммарному трафику.
Message size limit и max messages/sec.
WAF/бот-фильтры на этапе хэндшейка; защита от connection flooding (много коротких коннектов).
Изоляция по tenant/namespace: отдельные пулы ресурсов.
11) Наблюдаемость
Метрики:- `connections_active`, `connections_new_total`, `bytes_in/out`,
- `messages_in/out_total`, `dropped_messages_total`,
- `reconnects_total`, `latency_delivery_ms{p50,p95,p99}`.
- Логи: IP/UA, userId/tenantId, причина закрытия (`close_code`), длительность.
- Трейсинг: связывайте события с исходной командой (корреляционные ID); для WS используйте «виртуальные» спаны по батчам.
12) Эксплуатационные нюансы
TLS-терминация ближе к клиенту (CDN/edge).
Проактивная ротация соединений (graceful) при деплоях: отдайте флаг «переподключись».
Шардирование по ключу, чтобы равномерно распределять «горячие» каналы.
Снимки состояния для поздних подписчиков (snapshot + delta).
Кэш последнего значения (SSE особенно) — полезно для «холодного» клиента.
13) Анти-паттерны
Стрим больших бинарных кусков через SSE или JSON по WS — используйте загрузку по HTTP и ссылку в сообщении.
Авторизация только в момент подключения и отсутствие пере-проверок на долгих сессиях.
Глобальный sticky без нужды → дисбаланс и «горячие» ноды.
Отключенные таймауты/лимиты → зависшие коннекты съедают пул.
Буферизация SSE прокси/CDN → «реальное время» превращается в минуты задержки.
Отсутствие sequence/offset → после reconnect клиент теряет целостность данных.
14) Чек-лист внедрения
- Выбрана модель: WS (двунаправленно) или SSE (односторонне).
- Настроены таймауты, keepalive и reconnect (backoff+джиттер).
- Спроектированы sequence/offset и (для SSE) `id`/`Last-Event-ID`.
- Определены лимиты: размер/скорость/соединения/квоты, защита от DoS.
- Конфиг прокси/ингресса: upgrade, proxy_buffering off (SSE), read/send timeouts.
- Масштабирование: pub/sub-брокер, фан-аут-шлюз, sticky только при необходимости.
- Аутентификация: безопасная передача токена, ротация без обрыва.
- Наблюдаемость: метрики, логи, трассы; дашборды и алерты.
- План релизов: graceful-drain соединений, сигнал клиенту о пере-подключении.
- Game Days: обрывы сети, падение ноды, перегруз брокера, длинные RTT.
15) FAQ
Можно ли кэшировать SSE через CDN?
Обычно нет: это персонализированный поток. Для публичных каналов — возможно с коротким TTL и chunked-delivery, но легко наломать «реальное время».
Работает ли WebSocket поверх HTTP/2/3?
Браузерный WS стартует с HTTP/1.1-upgrade; есть RFC 8441 для h2, поддержка в прокси/сервере требуется отдельно. С h3 — в движении; для стриминга в h3 есть WebTransport, но это другой API.
gRPC vs WS для браузера?
Чистый браузер не говорит gRPC; нужен gRPC-Web через Envoy. Для интерактивных UI часто проще WS + REST.
16) Итоги
WebSocket — когда нужен диалог в реальном времени и компактный двунаправленный канал.
SSE — когда нужен простой и надежный push от сервера к клиенту, минимальная сложность и автоматический reconnect.
Успех в проде — это правильные таймауты и лимиты, восстановление с offset, pub/sub-фан-аут, корректные настройки прокси и четкая наблюдаемость.