惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

K
Kaspersky official blog
T
Threat Research - Cisco Blogs
N
News and Events Feed by Topic
Hacker News: Ask HN
Hacker News: Ask HN
Project Zero
Project Zero
Application and Cybersecurity Blog
Application and Cybersecurity Blog
博客园 - 叶小钗
Security Latest
Security Latest
Spread Privacy
Spread Privacy
aimingoo的专栏
aimingoo的专栏
N
News and Events Feed by Topic
Webroot Blog
Webroot Blog
U
Unit 42
Cyberwarzone
Cyberwarzone
小众软件
小众软件
Scott Helme
Scott Helme
Engineering at Meta
Engineering at Meta
Microsoft Security Blog
Microsoft Security Blog
T
The Blog of Author Tim Ferriss
A
About on SuperTechFans
爱范儿
爱范儿
S
Schneier on Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Schneier on Security
Schneier on Security
Latest news
Latest news
GbyAI
GbyAI
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Simon Willison's Weblog
Simon Willison's Weblog
The Register - Security
The Register - Security
WordPress大学
WordPress大学
博客园_首页
Blog — PlanetScale
Blog — PlanetScale
PCI Perspectives
PCI Perspectives
Jina AI
Jina AI
AI
AI
NISL@THU
NISL@THU
I
Intezer
G
GRAHAM CLULEY
B
Blog
S
Secure Thoughts
IT之家
IT之家
宝玉的分享
宝玉的分享
Recent Announcements
Recent Announcements
Y
Y Combinator Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
酷 壳 – CoolShell
酷 壳 – CoolShell
有赞技术团队
有赞技术团队
V2EX - 技术
V2EX - 技术
Recorded Future
Recorded Future
Hacker News - Newest:
Hacker News - Newest: "LLM"

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
WebSocket глазами системного аналитика и архитектора
Владимир Бурмистров · 2026-06-26 · via Все публикации подряд на Хабре

Средний

13 мин

2

Привет, Хабр. Меня зовут Владимир Бурмистров, я главный системный аналитик холдинга Т1. В этой статье хочу посмотреть на WebSocket глазами системного аналитика и архитектора: от конкретики протокола HTTP 101 и фреймов до архитектурных решений с API Gateway, sticky‑sessions и формата постановок задач.

Материал основан на реальном опыте из высоконагруженной системы, где живут в одном «зоопарке»:

  • REST‑API;

  • WebSocket;

  • GraphQL;

  • gRPC;

  • Kafka;

  • Redis (кеш и pub/sub);

  • WebRTC для видео.

С таким набором очень быстро становится понятно: WebSocket — не модная игрушка, а инструмент для узкого, но важного класса задач.

Зачем системному аналитику вообще думать о WebSocket?

Во многих проектах системный аналитик живёт в уютном мире REST: ресурсы, методы, CRUD, contract‑first и прочий знакомый набор. API реального времени и WebSocket кажутся чем‑то «для финтеха, трейдинга и игр».

Но стоит появиться хотя бы одной из подобных задач:

  • групповые чаты с «живыми» индикаторами набора и доставкой сообщений без перезагрузки;

  • совместное редактирование документов (Confluence, Google Docs);

  • совместные доски (Miro‑подобные);

  • realtime‑уведомления и статусы;

  • онлайн‑мониторинги, где задержка критична.

...как REST начинает тянуть архитектуру вниз: polling, long‑polling, костыли вокруг частых запросов и растущей нагрузки.

WebSocket как раз и закрывает класс задач, в которых:

  • важна двусторонняя связь (client ↔ server);

  • нужны минимальные задержки;

  • нужно сократить сетевой overhead от повторных HTTP‑заголовков;

  • много одновременно подключённых пользователей.

Как WebSocket живёт поверх HTTP и TCP

Upgrade: переход с HTTP на WebSocket

WebSocket не возникает «магически» сам по себе — он запускается с обычного HTTP‑запроса, в котором клиент просит сервер сменить протокол.

Рассмотрим пример HTTP‑handshake.

Запрос клиента:

GET /ws/chat HTTP/1.1
Host: example.com
Connection: Upgrade
Upgrade: websocket
Sec‑WebSocket‑Version: 13
Sec‑WebSocket‑Key: dGhlIHNhbXBsZSBub25jZQ==

Ответ сервера:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec‑WebSocket‑Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Код HTTP 101 Switching Protocols означает, что сервер согласился перейти с HTTP на другой протокол, в нашем случае — WebSocket.

Ключевой момент для аналитика: WebSocket‑канал создаётся после успешного HTTP‑апгрейда, и до этого момента у вас самый обычный HTTP‑запрос с заголовками и всеми ограничениями прокси и шлюзов.

ws:// и wss://: схемы URI

После установления соединения с точки зрения клиента мы имеем адреса такого вида:

  • ws://example.com/ws/chat — незашифрованный WebSocket;

  • wss://example.com/ws/chat — WebSocket поверх TLS (аналог HTTPS).

Это важно, потому что:

  • на схемах интеграций вы сразу видите, где REST (https://), а где realtime‑канал (wss://);

  • для ИБ и DevOps это разные потоки трафика с разными правилами.

Внутренности WebSocket: фреймы, payload и дельты

Структура фрейма

WebSocket передаёт данные не строками, а фреймами. Упрощённо:

  • FIN — флаг, последний ли это фрейм сообщения;

  • OPCODE — тип фрейма (текст, бинарный, ping, pong, close);

  • MASK + MASKING-KEY — маскирование данных (клиент → сервер обязателен);

  • PAYLOAD-LENGTH — длина тела;

  • PAYLOAD — полезная нагрузка (текст, бинарные данные).

Для аналитика важны выводы:

  • сообщение может быть разбито на несколько фреймов (важно для больших бинарников);

  • есть отдельные служебные фреймы ping/pong для heartbeat’а;

  • в общем случае мы оперируем на уровне «сообщения» (message), а не отдельных фреймов, но для высоконагруженных сценариев знание про фреймы помогает объяснить странные баги.

Пример текстового payload (чат)

Обычное событие «новое сообщение в чате» может выглядеть так:

{
  "type": "chat.message.new",
  "chatId": "c_12345",
  "messageId": "m_67890",
  "senderId": "u_100500",
  "createdAt": "2026-02-09T13:45:12.123Z",
  "text": "Всем привет!",
  "attachments": [
    {
      "id": "att_1",
      "type": "image",
      "url": "https://cdn.example.com/att_1.png"
    }
  ]
}

Ключевое для постановки:

  • type — тип события внутри одного WebSocket‑канала;

  • идентификаторы сущностей (chatIdmessageIdsenderId);

  • поля для состояния интерфейса (наличие вложений, статусы прочтения и прочее).

Пример дельта‑payload (whiteboard)

Для совместной доски нет смысла гонять весь документ.

{
  "type": "board.elements.updated",
  "boardId": "b_42",
  "version": 157,
  "authorId": "u_100500",
  "changes": [
    {
      "elementId": "el_10",
      "op": "move",
      "from": { "x": 100, "y": 200 },
      "to":   { "x": 130, "y": 210 }
    },
    {
      "elementId": "el_11",
      "op": "text.update",
      "prev": "Hello",
      "next": "Hello, world"
    }
  ],
  "timestamp": "2026-02-09T13:45:12.123Z"
}

Здесь важно:

  • наличие версии (version) для разрешения конфликтов;

  • список изменений (changes), а не полное состояние доски;

  • операции (op) явно описаны и могут быть расширяемыми.

Heartbeat, мёртвые сессии и SLA на задержку

Ping/pong и heartbeat на прикладном уровне

Протокол WebSocket поддерживает ping/pong‑фреймы, но в браузерных API они не всегда доступны. Поэтому часто используют прикладочный heartbeat — обычные сообщения ping/pong с JSON.

Типовой контракт:

// Пинг от клиента
{
  "type": "ping",
  "timestamp": 1760000000000
}

// Понг от сервера
{
  "type": "pong",
  "timestamp": 1760000000000,
  "serverTime": 1760000000100,
  "latency": 100
}

На сервере дополнительно ведут метрики по соединениям:

  • connectedAt;

  • lastPingTime;

  • lastPongTime;

  • latencyHistory;

  • missedHeartbeats.

Дальше по таймеру проверяют:

  • если timeSinceLastPing > HEARTBEAT_INTERVAL * MAX_MISSED_HEARTBEATS — соединение закрыть;

  • при закрытии чистят состояние (карты соединений, внутренние subscription’ы).

SLA на задержки: как это формализовать в требованиях

Аналитик может и должен задавать рамки. Примеры:

  • чаты:

    • целевой SLA задержки доставки сообщения — до 100 мс для 95‑го перцентиля;

    • максимально допустимая задержка для 99‑го перцентиля — до 500 мс;

  • уведомления: задержка до 5 секунд считается нормальной, дальше пользователь может считать уведомление «запоздавшим»;

  • whiteboard: для плавного UX при перемещении фигур задержка не должна превышать 50–100 мс.

Такие числа помогают бэкенду и архитектуре:

  • заложить нужную конфигурацию брокеров и кешей;

  • понять, нужен ли отдельный WebSocket‑кластер под определённую функциональность;

  • рассчитать нагрузку и необходимость горизонтального масштабирования.

WebSocket в микросервисной архитектуре: схемы и паттерны

Базовая картина: выделенный WebSocket‑сервис

Типовой набор контейнеров (уровень C4‑Container):

  • Client (Web/App) устанавливает соединение wss://ws.example.com/ws;

  • API Gateway принимает WebSocket‑handshake, пробрасывает Upgrade/Connection в бэкенд;

  • WebSocket Service хранит сессии пользователей и маршрутизирует события (чаты, доски, уведомления) подписчикам;

  • Business Services:

    • Chat Service — владеет логикой чатов и хранением сообщений;

    • Board Service — отвечает за whiteboard;

    • Notification Service — моделирует жизненный цикл уведомлений;

  • Transport Layer:

    • Kafka/NATS/RabbitMQ — событийная шина;

    • Redis (pub/sub, кеш) для быстрых fan‑out‑рассылок.

Основная идея в том, что WebSocket‑сервис — это транспортный слой, который не содержит тематическую бизнес‑логику. Он выполняет роль «концентратора» соединений и «коннектора» между бизнес‑событиями (Kafka/Redis) и конкретными пользователями.

API Gateway и Upgrade‑заголовки

Чтобы WebSocket работал через API Gateway (например, nginx), нужно не забыть пробросить необходимые заголовки.

Пример конфигурации nginx:

location /ws/ {
    proxy_pass http://ws-backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

Без этого апгрейд не произойдёт: шлюз будет видеть обычный HTTP‑запрос и не создаст WebSocket‑туннель, в результате клиент останется в состоянии «101 Switching Protocols» или получит ошибку.

Sticky‑sessions и несколько экземпляров WebSocket

При горизонтальном масштабировании WebSocket‑сервиса возникают проблемы:

  • у одного пользователя может быть несколько устройство, а значит и несколько WebSocket‑сессий;

  • разные пользователи, участвующие в одном чате, могут оказаться на разных экземплярах.

Типовые подходы к решению:

  1. Sticky‑sessions на балансировщике. Пользователя по cookie, IP или хешу userId всегда отправляют на один и тот же экземпляр (до смены конфигурации). Это уменьшает хаос, но не решает проблему fan‑out между экземплярами.

  2. Внутренний pub/sub (Redis, Kafka). Любое событие, пришедшее в Chat Service, публикуется в топик вида chat.{chatId} в Kafka или Redis pub/sub. Все экземпляры WebSocket‑сервиса подписаны и доставляют сообщения только тем подключённым пользователям, которые висят у них в памяти.

  3. Распределённые карты сессий. Хранилище вида userId -> [connectionId, instanceId...] помогает:

    • быстро находить, где сидит пользователь;

    • корректно закрывать все его сессии при logout;

    • реализовать бизнес‑ограничения: «не более N сессий на пользователя».

Ограничения браузеров и конкуренция за соединения

Исторически браузеры ограничивали количество одновременных HTTP‑соединений к одному домену (часто 6). Для WebSocket лимиты другие и обычно больше, но «бесконечными» они не являются. Практическое следствие:

  • если вы держите несколько WebSocket‑подключений к одному домену, нужно закладываться на ограничение;

  • в сложных случаях используют разные поддомены (ws1.example.comws2.example.com), чтобы распределить нагрузку.

Поэтому в реальных проектах чаще делают один WebSocket‑канал под чаты и второй WebSocket‑канал под остальные realtime‑функции. Всё остальное решается мультиплексированием по типу события внутри одного соединения (type в payload’е).

WebSocket, SSE и long‑polling: когда что выбирать

Чтобы системный аналитик и архитектор говорили с разработчиками на одном языке, полезно иметь простую «матрицу решений» по realtime‑технологиям.

Характеристика

WebSocket

SSE (Server‑Sent Events)

Long‑polling

Направление данных

Двустороннее (клиент ↔ сервер).

Одностороннее (сервер → клиент).

Клиент инициирует запрос, сервер отвечает при появлении данных.

Протокол

Отдельный протокол поверх TCP, старт через HTTP Upgrade.

Чистый HTTP (поток текстовых событий).

Чистый HTTP (длинный запрос + немедленный повтор)

Формат данных

Текст и бинарные фреймы.

Только текст (UTF‑8), чаще всего JSON.

Любые данные в HTTP‑ответе (обычно JSON).

Сложность реализации

Наиболее высокая (инфраструктура, масштабирование, отладка).

Средняя, проще WebSocket.

Самая простая, «просто HTTP».

Поддержка браузерами

Все современные браузеры.

Все современные браузеры, кроме очень старых IE.

Везде, где есть HTTP.

Автоматическое переподключение

Нужно реализовывать самому (или библиотеками).

Есть встроенный механизм EventSource + перезапуск.

Реализуется на клиенте циклом запросов.

Поддержка через прокси и балансировщики

Требует поддержки Upgrade, иногда блокируется сетевой инфраструктурой

Легче проходит, так как это обычный HTTP‑поток.

Максимально совместимо (обычный HTTP).

Типичные сценарии использования

Чаты, игры, совместное редактирование, управление устройствами, трейдинг.

Ленты событий, уведомления, поток журналов и метрик.

Уведомления и обновления в системах, не являющихся realtime, когда WebSocket/SSE не подходят.

Как выбирать на уровне требований

  1. Нужна двусторонняя связь и частые события (чаты, доски, игры, трейдинг). Выбор почти всегда WebSocket.

  2. Нужно только пушить данные на клиент (уведомления, ленты, мониторинг) с умеренной частотой. Рассматриваем SSE:

    • простой API EventSource в браузере;

    • обычный HTTP, проще ИБ и сетевикам;

    • автоматическое переподключение и события open, error, message.

  3. Очень ограниченная инфраструктура, корпоративная сеть режет WebSocket и SSE, есть только HTTP/1. Используем long‑poll’инг:

    • сервер держит запрос до появления данных или таймаута;

    • больше overhead’а по заголовкам, хуже масштабируется, но работает «везде».

  4. Гибридный подход. Нормальная архитектура может комбинировать:

    • WebSocket для интерактивных фич (чаты, доски, курсоры);

    • SSE и long‑polling для «мягких» уведомлений и аналитических лент.

Усиленные шаблоны постановки задач

Ниже описаны три более подробных шаблона: для whiteboard, индикаторов набора текста и уведомлений.

Whiteboard (совместная доска)

Событие: изменение элементов доски

  • Событие: board.elements.updated.

  • Транспорт: WebSocket (wss://ws.example.com/ws).

  • Инициатор: клиент (пользователь двигает фигуру или правит текст), изменение подтверждается Board Service.

  • Получатели: все активные участники доски boardId, кроме (опционально) инициатора.

Направление:

  • Клиент → WebSocket‑сервис → Board Service (через Kafka/REST/GRPC);

  • Board Service проверяет и сохраняет, публикует событие board.elements.updated;

  • WebSocket‑сервис рассылает событие всем подписчикам доски.

JSON‑схема payload

{
  "type": "board.elements.updated",
  "boardId": "string",
  "version": "integer",
  "authorId": "string",
  "changes": [
    {
      "elementId": "string",
      "op": "create|update|delete|move|resize",
      "prev": { "nullable": true },
      "next": { "nullable": true }
    }
  ],
  "timestamp": "string (ISO-8601)"
}

Требования:

  • version — монотонно растущая версия доски, используется для разрешения конфликтов;

  • rev и next содержат минимально необходимое состояние элемента (например, координаты, размеры, текст);

  • размер changes в одном событии — не более 100 элементов (остальное батчится).

Нагрузка и SLA

  • Пиковое количество активных пользователей на одной доске: до 50.

  • Максимальное количество событий board.elements.updated на доску: до 200/сек.

  • SLA задержки между фиксацией изменений в Board Service и доставкой в WebSocket‑клиент:

    • 95‑й перцентиль — до 100 мс;

    • 99‑й перцентиль — до 250 мс.

Надёжность

  • Потеря одного события допустима, так как клиент при переподключении обязан запросить полное состояние доски: GET /boards/{boardId}/state?version={clientVersion}.

  • В случае расхождений Board Service возвращает дельты или полное состояние.

Безопасность

  • Пользователь должен иметь право доступа к boardId (ACL).

  • Подписка на события доски оформляется отдельным сообщением:

{
  "type": "board.subscribe",
  "boardId": "b_42"
}

Индикатор набора текста («user is typing…»)

Это типичный пример события, которое не сохраняется в БД и чисто realtime.

Событие: начало и окончание набора

  • Событие: chat.typing.

  • Транспорт: WebSocket (общий канал чатов).

  • Инициатор: клиент (пользователь начинает или заканчивает набор).

  • Получатели: все участники чата chatId, кроме инициатора.

JSON‑схема payload

{
  "type": "chat.typing",
  "chatId": "string",
  "userId": "string",
  "state": "started|stopped",
  "timestamp": "string (ISO-8601)"
}

Правила отправки с клиента:

  • state = started отправляется не чаще одного раза в две секунды при непрерывном наборе;

  • state = stopped отправляется при потере фокуса поля ввода или отсутствии ввода дольше N секунд.

Нагрузка и SLA

  • Пиковое количество активных набирающих пользователей в одном групповом чате: до 20.

  • Ограничение на частоту отправки: максимум 10 событий/сек на чат по chat.typing.

  • SLA: задержка не критична, но желательно до 500 мс для комфортного UX.

Надёжность

  • Потеря событий chat.typing допустима, они не влияют на бизнес‑данные.

  • Не требуется повтора при переподключении.

Безопасность

  • Те же права, что и на chat.message.*: видеть typing event можно только в чате, где состоит пользователь.

Уведомления: WebSocket, SSE и long‑polling

Предположим, что у нас есть общая модель уведомления:

{
  "id": "string",
  "userId": "string",
  "type": "task.assigned|comment.added|system.alert|...",
  "title": "string",
  "body": "string",
  "createdAt": "string (ISO-8601)",
  "read": "boolean",
  "data": {
    "...": "..."
  }
}

WebSocket‑событие notification.new

Когда использовать:

  • пользователь уже в «толстом» клиенте с WebSocket;

  • нужны мгновенные реакции в интерфейсе (бейджи, всплывашки).

Событие: notification.new

{
  "type": "notification.new",
  "notification": {
    "id": "n_123",
    "userId": "u_42",
    "type": "task.assigned",
    "title": "Новая задача",
    "body": "Вам назначена задача #12345",
    "createdAt": "2026-02-09T13:45:12.123Z",
    "read": false,
    "data": {
      "taskId": "12345"
    }
  }
}

Нагрузка и SLA:

  • до 5 000 уведомлений/сек по системе;

  • SLA задержки: до 2 секунд.

Надёжность:

  • Если событие по WebSocket потеряно, то клиент при подключении должен сделать REST‑запрос: GET /notifications?since=lastSeenId.

SSE‑канал GET /sse/notifications

Когда использовать:

  • нужен только поток сервер → клиент;

  • интерфейс не держит WebSocket по другим причинам;

  • нужно более «мягкое» решение для инфраструктуры.

Клиент (псевдокод):

const evtSource = new EventSource("/sse/notifications");

evtSource.onmessage = (event) => {
  const payload = JSON.parse(event.data);
  // payload = notification.new, как в WebSocket-примере
};

evtSource.onerror = (err) => {
  // лог, UI-индикация, возможный fallback на long-polling
};

Формат серверного ответа (SSE):

event: notification.new
data: {"id":"n_123","userId":"u_42","type":"task.assigned", ...}

Переподключение и позиционирование по Last-Event-ID можно использовать для восстановления пропущенных событий.

Long‑polling GET /notifications/stream

Когда использовать:

  • в старых клиентах;

  • когда WebSocket и SSE заблокированы корпоративной сетью;

  • в очень простой архитектуре (минимум инфраструктурных изменений).

Протокол:

  1. Клиент отправляет GET /notifications/stream?since=lastSeenId.

  2. Сервер держит соединение до появления новых уведомлений или истечения таймаута (например, 30 секунд).

  3. Сервер отвечает списком новых уведомлений (может быть пустым).

  4. Клиент сразу отправляет следующий запрос.

Достоинства:

  • работает поверх обычного HTTP;

  • дружелюбен к прокси и фаерволам.

Недостатки:

  • у каждого ответа — полный HTTP overhead (заголовки и так далее);

  • хуже масштабируется при большом количестве пользователей и частых событиях.

Как аналитикам и архитекторам прокачаться в технической части WebSocket

Чтобы WebSocket перестал быть «чёрной коробкой», полезно сделать несколько шагов.

  1. Разобрать руками HTTP‑handshake:

    • увидеть реальный GET с Upgrade/Connection;

    • изучить ответ 101 Switching Protocols.

  2. Через Postman и Insomnia подключиться к тестовому WebSocket‑серверу (echo‑эндпоинты, аналоги websocket.org).

  3. Составить свой небольшой шаблон постановки задачи на WebSocket‑событие, содержащий:

    • тип события;

    • инициатора и получателя;

    • JSON‑схему;

    • SLA и нагрузку;

    • требования по надёжности и безопасности.

  4. Нарисовать (хотя бы текстом) C4‑схему, где:

    • WebSocket вынесен в отдельный микросервис;

    • ходит через API Gateway;

    • события проходят через брокер (Kafka/Redis) между бизнес‑сервисами и WebSocket‑слоем.

После этого разговоры про realtime‑API перестают быть «высоким искусством разработчиков» и становятся обычным, пусть и более сложным, инструментом в наборе системного аналитика и архитектора.