Урок по треку System design 8 мин

Очереди, брокеры и события

Kafka и RabbitMQ, гарантии доставки, идемпотентность, outbox, ретраи и DLQ.

Очередь развязывает отправителя и получателя: продюсер кладёт сообщение и идёт дальше, консьюмер обрабатывает, когда может. Это даёт асинхронность, сглаживание пиков и независимость сервисов.

Когда нужна очередь

  • Долгая работа, которую не нужно ждать пользователю: письма, обработка видео, генерация отчётов.
  • Пиковая нагрузка: очередь копит сообщения, консьюмеры разбирают с постоянной скоростью.
  • Несколько подписчиков на одно событие: «заказ оплачен» нужно и складу, и уведомлениям, и аналитике.

Kafka и RabbitMQ

Kafka это распределённый лог. Сообщения пишутся в партиции топика и хранятся заданное время (например, 7 дней), даже после прочтения. Каждая группа консьюмеров сама хранит смещение (offset) и может перечитать историю. Порядок гарантирован только внутри партиции. Сообщения с одинаковым ключом попадают в одну партицию. Масштаб: сотни тысяч и миллионы сообщений в секунду на кластер.

Важно: в группе консьюмеров одну партицию читает не больше одного консьюмера. Если партиций 6, а консьюмеров 10, то 4 будут простаивать.

RabbitMQ это классический брокер с очередями и маршрутизацией (exchange: direct, topic, fanout). Сообщение удаляется после подтверждения (ack). Удобен для очередей задач, сложной маршрутизации, приоритетов. Перечитать прошлое нельзя.

Короткое правило: поток событий, который читают многие и, возможно, повторно, берём Kafka. Очередь задач с маршрутизацией и подтверждениями берём RabbitMQ.

Гарантии доставки

  • At-most-once: не больше одного раза, возможна потеря.
  • At-least-once: не меньше одного раза, возможны дубли. Самый частый режим.
  • Exactly-once: в чистом виде через сеть недостижимо. Kafka даёт exactly-once внутри своей экосистемы (идемпотентный продюсер и транзакции), но побочный эффект во внешней системе всё равно нужно делать идемпотентным.

Практический вывод: ставим at-least-once и делаем обработчик идемпотентным. Например, храним id обработанных сообщений в таблице с уникальным индексом, или используем upsert.

Transactional outbox

Проблема двойной записи: сервис сохранил заказ в БД и должен отправить событие в Kafka. Если упадёт между этими шагами, событие потеряется или будет отправлено без заказа.

Решение: в той же транзакции, что и заказ, пишем событие в таблицу outbox. Отдельный процесс (или CDC через Debezium) читает outbox и публикует в брокер, отмечая отправленное. Доставка at-least-once, консьюмеры идемпотентны.

Ретраи и DLQ

Если обработка падает, сообщение повторяют с экспоненциальной задержкой. После N попыток отправляют в dead letter queue, чтобы одно «ядовитое» сообщение не блокировало очередь. DLQ мониторят и разбирают.

Backpressure

Если консьюмеры не успевают, растёт отставание (consumer lag). Метрика lag главная для мониторинга очередей. Решения: добавить консьюмеров (и партиций), ускорить обработку, батчить.

Порядок сообщений

Глобальный порядок дорог. Обычно хватает порядка по ключу: все события одного заказа идут в одну партицию по order_id.

Частые ошибки

  • Считать, что брокер даст exactly-once и не делать идемпотентность.
  • Писать в БД и в брокер без outbox.
  • Делать консьюмеров больше, чем партиций, ожидая ускорения.
  • Нет DLQ: одно битое сообщение останавливает обработку.

Что спросят на собесе

  • Чем Kafka отличается от RabbitMQ.
  • Какие гарантии доставки бывают и что такое идемпотентный консьюмер.
  • Как атомарно сохранить данные и отправить событие (outbox).
  • Как в Kafka обеспечить порядок событий одного заказа.
  • Что делать, если консьюмеры не успевают.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.