Транспорт бэкенда

System Design: Event Bus как в Uber

Uber Trip начинается: rider жмёт 'Request'. За следующие 10 секунд 20+ сервисов должны узнать об этом - pricing, matching, ETA, analytics, notifications. Как это сделать без того чтобы Trip Service знал обо всех 20 подписчиках?

  • **Uber uEvents** - внутренняя Kafka платформа: триллионы events в день, Schema Registry с тысячами схем, автоматический DLT для каждого критичного топика
  • **LinkedIn Kafka** обрабатывает 7 триллионов сообщений в день (2024): activity feed, notifications, analytics, ads - всё через единый event bus с Schema Registry
  • **Cloudflare** использует Kafka в 200+ PoP: HTTP logs (20M events/s), Workers analytics, DDoS events реплицируются через MirrorMaker 2 между всеми DC в реальном времени

Event Bus Architecture

Event Bus - это централизованная шина событий, через которую сервисы общаются асинхронно. Uber's uEvents обрабатывает триллионы событий в день через Kafka: trip.started, driver.location, payment.charged. Ключевые компоненты: брокер (Kafka/Pulsar), schema registry (Confluent), consumer groups, topic partitioning. В отличие от point-to-point messaging, event bus реализует publish-subscribe: один producer, много consumers.

Partitioning key - критическое решение. Partition by user_id гарантирует ordering всех событий одного пользователя (нужно для trip lifecycle). Partition by random UUID максимизирует throughput, но теряет ordering. LinkedIn использует member_id как partition key для всех activity events - consumer видит события одного пользователя в порядке их создания.

Kafka топик `ride.events` партиционирован по `driver_id`. Driver A создаёт события на partition 3. Что гарантирует Kafka?

Schema Management

Schema Registry (Confluent) - центральный репозиторий схем событий. Каждое событие в Kafka содержит 5-байтовый magic header: `0x00` + 4-байтовый schema_id. Consumer получает schema_id, запрашивает Schema Registry, десериализует Avro/Protobuf. Без schema registry эволюция схем - хаос: producer добавил поле, consumer не знает как читать.

Protobuf vs Avro: Protobuf - field numbers вместо имён, бинарный, forward-compatible by default. Avro - schema embedded в файл (для Hadoop) или через registry (для Kafka), более компактный. Uber перешёл с JSON на Protobuf для Kafka events - размер сообщений уменьшился в 5-10x, latency P99 снизился на 30% благодаря меньшему network I/O.

Avro schema с compatibility mode BACKWARD. Producer добавил обязательное поле `region` без default. Что произойдёт со старыми consumers?

Dead Letter Replay

Dead Letter Topic (DLT) в Kafka - это отдельный топик куда попадают сообщения, которые consumer не смог обработать после max_retries. В отличие от RabbitMQ DLQ, Kafka DLT позволяет replay: исправить баг в consumer, прочитать DLT с offset 0 и обработать все failed события заново. Netflix использует DLT для каждого критичного топика с retention 30 дней.

Retry topology: вместо задержки в одном consumer (Thread.sleep), каждый retry уровень - отдельный топик с dedicated consumer group. Consumer читает `retry-1`, проверяет `retry-after` header, если время не пришло - делает pause(partition, 1000ms), если пришло - обрабатывает. Это не блокирует главный consumer и позволяет fine-grained monitoring каждого retry уровня.

Event bus хранит DLT 30 дней. Consumer баг исправлен на day 15. Как правильно сделать replay?

Multi DC Replication

Multi-DC Kafka replication решает проблему геораспределения: US-East cluster и EU-West cluster должны синхронизироваться. Kafka MirrorMaker 2 (MM2) - официальный инструмент для cross-cluster replication. Топики реплицируются с префиксом: `us-east.trip.events` на EU cluster. MM2 использует Kafka Connect под капотом и поддерживает bidirectional sync с loop prevention.

Active-Active vs Active-Passive: Active-Passive - один DC пишет, другой читает реплику (проще, но нет local write). Active-Active - оба DC пишут в свои топики, MM2 реплицирует в обе стороны (сложнее conflict resolution). Uber использует Active-Active с geofencing: поездка создаётся в ближайшем DC, но events реплицируются глобально для analytics. Cloudflare - Active-Active Kafka в 200+ PoP по всему миру.

MM2 реплицирует `trip.events` из us-east в eu-west. Consumer offset на eu-west отстаёт на 50 000 messages. Что это означает?

Exactly Once Delivery

Exactly-once semantics (EOS) в Kafka - это гарантия что каждое сообщение обработано ровно один раз, даже при broker failures и consumer restarts. Реализация: idempotent producer (enable.idempotence=true) + Kafka Transactions. Idempotent producer добавляет sequence number к каждому batch, broker дедуплицирует по (producer_id, sequence). Transaction: atomic write в несколько партиций + commit consumer offset.

EOS имеет цену: throughput падает на 20-30%, latency растёт из-за 2-phase commit. Поэтому EOS используется только для финансовых транзакций. Для аналитики и нотификаций достаточно at-least-once + idempotent consumers (deduplication по event_id). Flink и Spark Streaming поддерживают EOS через checkpoint mechanism - не Kafka transactions, но семантически эквивалентно.

Kafka автоматически гарантирует exactly-once delivery по умолчанию

По умолчанию Kafka даёт at-least-once. EOS требует явной настройки: enable.idempotence=true, transactionalId, и читатели должны использовать isolation.level=read_committed

EOS добавляет 20-30% overhead на throughput. Kafka правильно делает что не включает это по умолчанию - большинство use cases (analytics, logging) нормально работают с at-least-once + idempotent consumers, без цены транзакций

Consumer с EOS обработал payment event, написал в `payment.processed`, но crash произошёл до commit offset. Что случится при рестарте?

Итоги

  • **Schema Registry + Avro/Protobuf** - основа эволюции event bus: BACKWARD compatibility позволяет деплоить consumers раньше producers, FULL mode максимально строгий для финансовых событий
  • **Dead Letter Topic + replay** - принципиальное отличие от традиционных DLQ: Kafka DLT хранится дни/недели, позволяя replay после исправления бага без потери данных
  • **EOS только там где нужно**: финансы и инвентарь требуют Kafka Transactions, аналитика и нотификации обходятся at-least-once + idempotent consumers с лучшим throughput

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

Event Bus строится на основе паттернов из предыдущих уроков:

  • Event-Driven Architecture — Event Bus - реализация EDA: topic per event type, consumer groups как независимые подписчики, ordering guarantees через partitioning
  • Transactional Outbox — Outbox Pattern - типичный способ надёжно публиковать события в event bus: Debezium читает outbox таблицу и публикует в Kafka топик
  • Dead Letter Queue — Kafka DLT - эволюция концепции DLQ: те же retry стратегии, но с возможностью replay всей истории failures после исправления бага

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

  • Avro schema BACKWARD compatible: новый consumer читает старые данные. А что если нужно переименовать поле `user_id` в `rider_id`? Какова migration стратегия без downtime?
  • Event bus хранит события 7 дней для replay. Новый analytics сервис нужно 'догнать' до текущего момента, обработав 7 дней истории. Как это сделать не перегрузив production Kafka cluster?
  • Multi-DC Active-Active: US user создаёт trip в us-east, EU consumer видит его через MM2 репликацию. Если MM2 lag вырастает до 30 минут из-за network outage - какие downstream системы сломаются, а какие продолжат работу?

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

  • dist-07-transactions
System Design: Event Bus как в Uber

0

1

Войти