Real-Time Backend

Pub/Sub паттерн

Один клик «купить» на Amazon запускает десятки независимых процессов: склад резервирует товар, финансы списывают деньги, email-служба отправляет чек, аналитика фиксирует конверсию, fraud-detection проверяет транзакцию. Все они происходят параллельно - и ни один сервис не знает о существовании других. Это Pub/Sub в действии.

  • **Google Cloud Pub/Sub** обрабатывает более 500 млрд сообщений в день - весь трафик внутри Google (Gmail, YouTube, Search) маршрутизируется через Pub/Sub
  • **LinkedIn Kafka** пропускает 1 трлн+ сообщений в сутки: activity streams, metrics, audit logs - всё это fan-out через топики к десяткам downstream-сервисов
  • **Twitch** использует Pub/Sub для чата: одно сообщение стримера мгновенно доставляется 100 000+ одновременным зрителям канала через fan-out на уровне брокера
  • **Uber** публикует события `trip.status.changed` в Pub/Sub - и независимо подписанные сервисы уведомлений, billing и analytics обрабатывают их параллельно без координации между собой

Pubsub Concept

В классической архитектуре отправитель сообщения знает, кому оно предназначено - он держит ссылку на получателя и вызывает его напрямую. Это tight coupling: изменение получателя ломает отправителя. Pub/Sub разрывает эту связь. **Publisher** публикует сообщение в **брокер** (message broker) - и на этом его работа заканчивается. Он понятия не имеет, сколько **subscribers** получат сообщение и получат ли вообще. Subscriber, в свою очередь, не знает, кто опубликовал. Оба общаются через брокер - посредника, который маршрутизирует сообщения.

Ключевое свойство Pub/Sub - **temporal decoupling**: publisher и subscriber не обязаны работать одновременно. Kafka хранит сообщения на диске; subscriber может прийти через час и прочитать всё, что накопилось. Redis Pub/Sub - полная противоположность: сообщения эфемерны, и если subscriber отключён в момент публикации, он сообщение потеряет навсегда.

Какое фундаментальное свойство отличает Pub/Sub от прямых вызовов между сервисами?

Pubsub Topics

**Topic** (или **channel**) - это именованный канал в брокере, на который publisher отправляет сообщения, а subscribers подписываются. Topic отвечает на вопрос «что произошло»: `order.created`, `user.registered`, `payment.failed`. Subscriber выбирает только те topics, которые ему нужны - отдельный Inventory Service подписывается на `order.created`, Email Service - тоже на `order.created`, но Payment Service слушает `payment.failed`. Каждый получает только свой поток событий.

Системы различаются по гибкости именования topics. В **Kafka** topic - это жёстко определённая логическая единица с партициями и retention policy. В **Google Pub/Sub** - строка-идентификатор вида `projects/my-project/topics/orders`. В **Redis Pub/Sub** и MQTT поддерживаются **wildcards**: подписка на `sensor.temperature.*` автоматически покрывает `sensor.temperature.room1`, `sensor.temperature.room2` и любые новые каналы с этим префиксом.

Сервис хочет получать события из нескольких похожих каналов: `metrics.cpu`, `metrics.memory`, `metrics.disk` - и любых новых `metrics.*` в будущем. Какой механизм это поддерживает?

Pubsub Fanout

**Fan-out** - механизм, при котором одно сообщение доставляется нескольким subscribers одновременно. Это главная суперсила Pub/Sub. Без fan-out каждый новый потребитель данных требует явного изменения у источника. С fan-out - publisher публикует один раз, брокер сам делает N копий доставки. Twitch использует fan-out для чата: одно сообщение от стримера доставляется 100 000+ одновременных зрителей канала. Google Pub/Sub обрабатывает 500 млрд+ сообщений в день именно благодаря параллельной fan-out доставке.

Fan-out бывает двух видов. **Broadcast fan-out**: каждый subscriber получает собственную копию сообщения - так работает Redis Pub/Sub и большинство event bus систем. **Competing consumers (queue model)**: сообщение получает только один из subscribers группы - так Kafka распределяет нагрузку внутри consumer group. Оба паттерна нужны: broadcast для уведомлений, competing consumers для балансировки обработки.

Система обрабатывает загрузку видео: нужно конвертировать в несколько форматов (480p, 720p, 1080p) параллельно. Как правильно использовать fan-out?

Pubsub Impl

Выбор реализации Pub/Sub определяется тремя факторами: **durability** (нужно ли хранить сообщения), **throughput** и **latency**. Redis Pub/Sub - самый быстрый вариант (sub-millisecond latency), но полностью эфемерный: сообщение живёт только в момент доставки, если subscriber отключён - оно теряется. Kafka хранит все сообщения на диске с настраиваемым retention (часы, дни, бесконечно), что позволяет replay и обработку с задержкой. LinkedIn обрабатывает 1 трлн+ сообщений в день через Kafka. Google Pub/Sub - managed-решение с гарантиями доставки at-least-once, масштабируется автоматически.

  • **Redis Pub/Sub** - ephemeral, sub-ms latency, подходит для realtime нотификаций где потеря допустима (live чат, игровые события)
  • **Kafka** - durable, высокий throughput (миллионы msg/sec), replay, подходит для event sourcing и аналитических pipeline
  • **Google Cloud Pub/Sub** - managed, at-least-once delivery, автомасштабирование, 500 млрд+ msg/day в prod Google
  • **RabbitMQ** - гибкие routing rules (exchanges), подходит для сложных workflow с routing по атрибутам
  • **NATS** - легковесный, cloud-native, поддерживает как ephemeral так и JetStream (durable), активен в Kubernetes-экосистеме

Pub/Sub и очередь сообщений (message queue) - одно и то же

Message queue обычно реализует competing consumers: одно сообщение обрабатывается одним consumer'ом. Pub/Sub реализует fan-out: одно сообщение получают все subscribers. Kafka умеет оба паттерна через разные конфигурации consumer group.

Путаница возникает потому, что RabbitMQ, Kafka и Redis используются и для очередей, и для Pub/Sub. Разница - в семантике доставки: queue гарантирует exactly-once обработку конкретным consumer'ом, Pub/Sub гарантирует доставку каждому подписанному subscriber'у.

Финтех-стартап строит систему: при каждой транзакции нужно обновить баланс, записать в аудит-лог и запустить fraud-detection. Ни одно из событий не должно теряться даже при падении сервиса. Что выбрать?

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

  • **Decoupling**: publisher не знает о subscribers - это позволяет добавлять новых consumers без изменения producer'а
  • **Fan-out**: одно сообщение в topic -> N subscribers получают копию параллельно; competing consumers (одна kafka consumer group) - для балансировки нагрузки
  • **Durability vs latency**: Redis Pub/Sub - ephemeral, sub-ms, для realtime; Kafka/Google Pub/Sub - durable, replay, для гарантий доставки в критичных системах

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

Pub/Sub - часть широкой экосистемы асинхронной коммуникации:

  • Message Queues — Альтернативная модель доставки: queue - competing consumers, Pub/Sub - broadcast fan-out; многие брокеры (Kafka, RabbitMQ) поддерживают оба паттерна
  • Event-Driven Architecture — Pub/Sub - основной транспортный механизм EDA: сервисы общаются исключительно через события, никаких прямых вызовов
  • WebSockets — WebSocket - канал доставки Pub/Sub-сообщений клиенту; backend Pub/Sub брокер -> сервер -> WebSocket -> browser

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

  • Если subscriber временно недоступен, какое поведение системы допустимо для текущего проекта: потеря события (Redis) или гарантированная доставка после восстановления (Kafka)?
  • Сколько независимых сервисов сейчас напрямую вызывают друг друга в цепочке? Какие из этих связей можно заменить Pub/Sub-подпиской для упрощения системы?
  • Broadcast fan-out или competing consumers - что нужно для каждого из существующих сценариев обработки данных в проекте?

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

  • net-55-message-queues
Pub/Sub паттерн

0

1

Войти