Real-Time Backend

Event-Driven архитектура

2011 год, LinkedIn: сайт падает под нагрузкой. Каждый сервис дёргает каждый напрямую - сотни HTTP-вызовов в цепочке. Решение - Kafka и переход на event-driven. Сегодня 700+ микросервисов LinkedIn общаются через события, обрабатывая 7 трлн сообщений в день.

  • Netflix хранит историю просмотров как журнал событий (Event Sourcing) - это позволяет рекомендательной системе реплеить историю и строить новые модели без миграций данных
  • Uber ETA-сервис подписан на события `driver.location.updated` от тысяч водителей и пересчитывает время прибытия реактивно - без polling и без прямых вызовов между сервисами
  • LinkedIn использует Kafka как центральный event bus: сервис вакансий публикует `JobPosted`, а рекомендации, аналитика и email-нотификации - независимые consumer-ы одного topic-а
  • Axon Framework реализует CQRS+ES в Java: команда `PlaceOrderCommand` превращается в `OrderPlacedEvent`, который хранится в EventStore и проецируется в несколько read model-ей

Event Driven

В Event-Driven Architecture (EDA) компоненты системы не вызывают друг друга напрямую - они публикуют **события** (events) и реагируют на события других. Событие - это неизменяемый факт: «заказ создан», «платёж прошёл», «водитель сдвинулся на 200 м». Никто не ждёт ответа - сервис опубликовал и двинулся дальше.

Uber строит ETA-сервис именно так: тысячи водителей ежесекундно генерируют события `driver.location.updated`, а сервис расчёта времени прибытия подписан на этот поток и пересчитывает маршруты реактивно, без polling и без прямых вызовов к сервису геолокации.

  • **Слабая связанность** - producer не знает, кто слушает и сколько их
  • **Масштабируемость** - consumer-ы добавляются без изменения producer-а
  • **Resilience** - сбой одного consumer не блокирует остальных и не роняет producer
  • **Audit trail** - поток событий сам по себе журнал изменений

Uber ETA-сервис потребляет события `driver.location.updated`. Что произойдёт, если ETA-сервис упадёт на 30 секунд?

Event Sourcing

Event Sourcing - паттерн хранения состояния: вместо snapshot-а текущего состояния в БД хранится **журнал всех событий**, которые к нему привели. Текущее состояние - это проекция (fold) поверх журнала. Netflix применяет ES для истории просмотров: каждое `VideoPlayed`, `VideoPaused`, `VideoCompleted` пишется в append-only лог. Состояние профиля - результат применения всех этих событий.

**Axon Framework** (Java) - production-ready реализация CQRS+ES. Каждый aggregate хранится как журнал доменных событий. Команда `PlaceOrderCommand` превращается в событие `OrderPlacedEvent`, которое записывается в `EventStore` и проецируется в read model. Команда из LinkedIn - используют ES для аудита изменений профилей пользователей.

  • **Полная история** - можно ответить «что было с заказом в 14:32 вчера»
  • **Debugging** - воспроизвести любой баг, реплеив события до момента сбоя
  • **Temporal queries** - состояние объекта на любой момент времени
  • **Компромисс** - eventual consistency между write и read model, сложность проекций

В системе с Event Sourcing разработчик хочет узнать остаток на счёте пользователя на 1 января 2024. Как это сделать?

Event Bus

Event Bus - инфраструктурный компонент, который принимает события от producer-ов и доставляет их subscriber-ам. Это центральная шина коммуникации в EDA. LinkedIn перевёл межсервисную коммуникацию на Kafka в 2011 году - Kafka и стал их event bus для 700+ микросервисов. Каждый сервис публикует события в topic-и, другие - читают.

  • **In-process bus** - библиотека внутри одного процесса (NestJS EventEmitter, Spring ApplicationEventPublisher). Не переживает рестарт.
  • **Message broker** - внешний сервис (Kafka, RabbitMQ, Redis Streams). Durable, cross-service, масштабируемый.
  • **Cloud event bus** - AWS EventBridge, GCP Pub/Sub. Managed, serverless-friendly.

**Consumer Group** - ключевой паттерн Kafka. Несколько instance одного сервиса объединяются в group и делят партиции между собой (параллельная обработка). Разные сервисы - разные groups - каждый получает все события независимо.

LinkedIn запускает новый сервис `RecommendationService`, который должен реагировать на события `ProfileUpdated`. Что нужно сделать в уже работающей Kafka-системе?

Event Replay

Event Replay - повторное воспроизведение исторических событий. Если журнал событий хранится достаточно долго (Kafka по умолчанию - 7 дней, Event Store - бессрочно), новый сервис может «прокрутить» всю историю с самого начала и построить актуальную проекцию. Netflix при запуске нового аналитического сервиса реплеит историю просмотров - миллиарды событий - и получает готовую модель без миграций.

  1. Новый сервис стартует с offset=0 (начало topic-а / event store)
  2. Читает события батчами, строит in-memory или persistent проекцию
  3. После catch-up переходит в real-time режим, читая новые события
  4. Старый сервис продолжает работать - zero downtime migration

**Pitfall:** при replay нельзя повторно запускать side effects (отправку email, списание денег). Проекция должна быть **idempotent** - применять событие дважды даёт тот же результат. Паттерн: хранить `event_id` и пропускать уже обработанные.

Event Replay и Event Sourcing - одно и то же

Event Sourcing - паттерн хранения состояния через журнал событий. Event Replay - операция воспроизведения этого журнала для построения новой проекции или отладки.

ES без replay возможен (просто хранение истории). Replay без ES - тоже: Kafka topic является журналом независимо от того, применяется ли ES в сервисах. Replay - инструмент, ES - архитектурный паттерн.

При replay событий `OrderPlaced` новый сервис отправляет приветственное письмо на каждое событие. Что произойдёт и как это исправить?

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

  • EDA: компоненты общаются через события, а не прямые вызовы - слабая связанность, независимое масштабирование
  • Event Sourcing: состояние = fold по журналу событий; полная история, temporal queries, debug через replay
  • Event Bus: инфраструктура доставки (Kafka, RabbitMQ, EventBridge) - producer не знает о consumer-ах, consumer groups обеспечивают параллелизм
  • Event Replay: воспроизведение журнала для новых проекций или отладки; side effects должны быть idempotent

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

EDA пересекается с несколькими архитектурными паттернами realtime-систем:

  • CQRS — Command Query Responsibility Segregation - естественная пара Event Sourcing: команды пишут события, queries читают проекции
  • Kafka Streams — Stream processing поверх event bus: stateful операции (join, aggregate, window) над потоками событий
  • Microservices — EDA - предпочтительный способ коммуникации между микросервисами вместо синхронных REST-вызовов
  • Saga Pattern — Распределённые транзакции через цепочку событий и компенсирующих команд - строится поверх event bus

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

  • Какой сервис в проекте выиграл бы от перехода на event-driven взаимодействие - где сейчас tight coupling создаёт проблемы?
  • Если завтра нужно добавить аналитику активности пользователей - как это проще сделать в EDA-системе vs синхронной?
  • Какие данные в приложении имеет смысл хранить как журнал событий (Event Sourcing), а не как текущее состояние?

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

  • net-55-message-queues
Event-Driven архитектура

0

1

Войти