Очередь развязывает отправителя и получателя: продюсер кладёт сообщение и идёт дальше, консьюмер обрабатывает, когда может. Это даёт асинхронность, сглаживание пиков и независимость сервисов.
Когда нужна очередь
- Долгая работа, которую не нужно ждать пользователю: письма, обработка видео, генерация отчётов.
- Пиковая нагрузка: очередь копит сообщения, консьюмеры разбирают с постоянной скоростью.
- Несколько подписчиков на одно событие: «заказ оплачен» нужно и складу, и уведомлениям, и аналитике.
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 обеспечить порядок событий одного заказа.
- Что делать, если консьюмеры не успевают.