WebSocket стримы и события
TL;DR
Рабочий стрим = надежный канал (WSS) + резюмируемые оффсеты + идемпотентные события + строгие лимиты и backpressure. Делайте: JWT-аутентификацию, авторизацию на топики, heartbeats, seq/offset+resume-токен, at-least-once + дедуп. Для масштаба — шардирование по user/tenant, sticky-роутинг, и очередь (Kafka/NATS/Redis Streams) как источник истины.
1) Бизнес-кейсы iGaming (что реально стримим)
Баланс/лимиты: мгновенные изменения баланса, лимитов RG, блокировок.
Ставки/раунды/результаты: подтверждение, статус, расчет выигрышей.
Турниры/лидерборды: позиции, таймеры, призовые события.
Платежи: статус payout/refund, KYC/AML флаги — как уведомления (а критика остается в REST+вебхуки).
Сервисные события: сообщения чата, push-баннеры, статусы сессии, maintenance.
2) Протокол и соединение
Только WSS (TLS 1.2+/1.3). Максимум 1 активное соединение на устройство/сессию по умолчанию.
Ping/Pong: клиент шлет `ping` каждые 20–30 с, таймаут ответа 10 с. Сервер сбрасывает соединение при 3 таймаутах подряд.
Компрессия: `permessage-deflate`, лимит на размер кадра (например, ≤ 64 KB).
Формат полезной нагрузки: JSON для внешних, Protobuf/MsgPack для внутренних/мобильных.
3) Аутентификация и авторизация
Хэндшейк с JWT в query/header (`Sec-WebSocket-Protocol`/`Authorization`), TTL токена короткий (≤ 15 мин), refresh по out-of-band (REST).
Tenant-scoped claims: `sub`, `tenant`, `scopes`, `risk_flags`.
ACL на топики/каналы: подписка только на разрешенные `topic` (например: `user:{id}`, `tournament:{id}`, `game:{table}`).
Пересоздание соединения при истечении токена: «мягкое окно» 60 с.
4) Модель подписок
Клиент после connect отправляет команды:json
{ "op":"subscribe", "topics":["user:123", "tournament:456"], "resume_from":"1748852201:987654" }
{ "op":"unsubscribe", "topics":["tournament:456"] }
`resume_from` — оффсет (см. §5), если клиент восстанавливает соединение.
Сервер отвечает ack/nack, непрошедшие ACL — в `nack` с `reason`.
5) Гарантии доставки и резюмирование
Цель: at-least-once на канал + идемпотентность у клиента.
Каждое событие имеет монотонный `seq` в рамках «партиции» (обычно user/room) и глобальный `event_id` для дедупа.
При ре-коннекте клиент передает `resume_from` = последний подтвержденный `seq` (или `offset` брокера). Сервер догружает пропущенные события из «источника истины» (Kafka/NATS/Redis Streams).
Если лаг превышает retention (например, 24 ч) — сервер присылает `snapshot` состояния и новый `seq`.
- Хранить `last_seq`/`event_id` в durable-хранилище (IndexedDB/Keychain).
- Дедуп по `event_id`, пропускать события с `seq ≤ last_seq`, обнаруживать дырки (gap) → авто-`resync` запроса снапшота.
6) Схема сообщения (envelope)
json
{
"ts": "2025-11-03T12:34:56. 789Z",
"topic": "user:123",
"seq": "1748852201:987654", // partition:offset
"event_id": "01HF..", // UUID/KSUID
"type": "balance. updated",
"data": { "currency":"EUR", "delta"--5. 00, "balance":125. 37 },
"trace_id": "4e3f.., "//for correlation
"signature": "base64 (hmac (...)) "//optional for partners
}
`type` — доменная таксономия (см. словарь событий).
PII/PCI — исключить/маскировать на уровне шлюза.
7) Backpressure, квоты и защита от «дорогих» клиентов
Server → Client: per-connection send-queue со «скользящим окном». Переполнен — сброс подписок на «шумные» топики или disconnect с кодом `1013`/`policy_violation`.
Client → Server: лимиты на `subscribe/unsubscribe` (например, ≤ 10/сек), ограничение списка топиков (≤ 50), минимальный интервал повторной подписки.
Rate limits по IP/tenant/ключу. Аномалии → временная блокировка.
Priority: жизненно важные события (баланс, RG-лимиты) — приоритетная очередь.
8) Защита и безопасность
WAF/бот-профиль на хэндшейк-эндпоинте, список разрешенных Origin.
mTLS между edge-шлюзом и стрим-узлами.
DoS-защита: SYN-cookies на L4, лимиты на число открытых WS/интервал keep-alive.
Anti-replay: `timestamp` в опциональной подписи полезной нагрузки (для партнеров) с допустимым окном 5 мин.
Изоляция арендаторов: физическое/логическое шардирование, ключи/токены per-tenant.
9) Транспортная архитектура
Шлюз (edge): терминейт TLS, authN/Z, квоты, маршрутизация на партицию.
Stream-узлы: stateless-воркеры с sticky-роутингом по `hash(user_id)%N`.
Брокер событий: Kafka/NATS/Redis Streams — источник истины и реплей-буфер.
State-сервис: хранит снапшоты (balance, позиции в турнире).
Мультирегион: актив-актив; GSLB по ближайшему региону; home-region закрепляется при логине; при фейловере — «холодный» резюм из другого региона.
10) Порядок, согласованность, идемпотентность
Упорядоченность гарантируется внутри партиции (user/room), не глобально.
Консистентность: событие может прийти раньше REST-ответа; UX должен уметь жить с промежуточным состоянием (optimistic UI + reconciliation).
Идемпотентность: повторная обработка `event_id` не меняет состояние клиента.
11) Ошибки, reconnect и «шторма»
Коды закрытия: `1000` (normal), `1008` (policy), `1011` (internal), `1013` (server overload).
Клиентский экспоненциальный backoff + jitter: 1s, 2s, 4s… макс 30s.
Во время массовых реконнектов («thundering herd») — сервер отдает `retry_after` и «серые» ответы с подсказкой использовать SSE fallback для read-only.
12) Кэш и снапшоты
Каждая подписка может начинаться со снапшота актуального состояния, затем — поток дифф-событий.
Версионирование схемы `data_version` и совместимость (расширение полей не ломает клиентов).
13) Наблюдаемость и SLO
Метрики:- Соединения: активные, установленные/сек, распределение по арендаторам/регионам.
- Доставка: p50/p95 задержки от брокера до клиента, drop-rate, resend-rate.
- Надежность: доля успешных резюмов без снапшота, gap-детектор.
- Ошибки: 4xx/5xx на хэндшейке, коды закрытия, лимит-хиты.
- Нагрузка: RPS команд `subscribe`, размер очередей, CPU/NET.
- Установление WS p95 ≤ 500 мс (внутри региона).
- End-to-end latency события p95 ≤ 300 мс (user-partition).
- Resume success ≥ 99%, message loss = 0 (по at-least-once).
- Uptime стрим-эндпоинта ≥ 99.95%.
14) Управление схемами и версиями
Словарь событий с владельцами, примерами и семантикой.
«Мягкая» эволюция: только добавление опциональных полей; удаление — после `@deprecated` периода.
Контрактные тесты против клиентских SDK, линтеры на JSON Schema/Protobuf.
15) Плейбуки инцидентов (встроить в ваш общий плейбук)
Рост latency: переключить партиции на резервные узлы, увеличить размер batch у брокера, включить приоритизацию жизненно важных событий.
Шторма реконнектов: активировать `retry_after`, временно поднять лимиты handshake, включить SSE-фоллбек.
Утечка токенов: ротация JWKS, отзыв затронутых токенов, принудительный reconnect с re-auth.
Потеря партиции брокера: перевод в режим снапшотов, реплей после восстановления.
16) Мини-спецификация API (упрощенно)
Handshake (HTTP GET → WS):
GET /ws? tenant=acme&client=web
Headers:
Authorization: Bearer <JWT>
X-Trace-Id: <uuid>
Команды клиента:
json
{ "op":"subscribe", "topics":["user:123"], "resume_from":"1748852201:42" }
{ "op":"unsubscribe", "topics":["user:123"] }
{ "op":"ping", "ts":"2025-11-03T12:34:56Z" }
Серверные ответы:
json
{ "op":"ack", "id":"subscribe:user:123" }
{ "op":"event", "topic":"user:123", "seq":"1748852201:43", "type":"balance. updated", "data":{...} }
{ "op":"snapshot", "topic":"user:123", "seq":"1748852201:42", "state":{...} }
{ "op":"error", "code":"acl_denied", "reason":"no access to topic tournament:456" }
{ "op":"pong", "ts":"..." }
17) UAT чек-лист
- Резюм из оффсета после 1/10/60 минут даунтайма клиента.
- Дедуп: повторная доставка того же `event_id` не меняет состояние.
- Gap-детектор → автоматический `snapshot` и выравнивание.
- Квоты и backpressure: нагруженный клиент получает policy-disconnect.
- Мультирегион: failover региона с сохранением оффсета.
- Security: токен-рокера, истекший JWT, попытка подписки вне ACL.
- RG/баланс события приходят раньше/после REST — UI корректно «сшивает».
18) Частые ошибки
Нет `seq/offset` и возобновления — теряем события и доверие.
Смешивание критичных платежных команд в WS-мутациях — используйте REST.
Отсутствие backpressure/квот — «подвешенные» соединения и лавина памяти.
Глобальная упорядоченность — дорого и не нужна; достаточно порядка в партиции.
Логирование PII в событиях — нарушения приватности и PCI/GDPR.
Отсутствие словаря событий и версионирования — ломаются клиенты.
Резюме
WebSocket-стримы дают реактивный UX и оперативные сигналы, если они построены как резюмируемый, защищенный и ограниченный канал: WSS+mTLS/JWT, ACL на топики, seq/offset+resume, at-least-once с дедупом, backpressure/квоты, брокер как источник истины, наблюдаемость и SLO. Так стримы остаются быстрыми для пользователя и управляемыми для платформы — без компромиссов по безопасности и деньгам.