Зачем брокер
Брокер сообщений развязывает producer и consumer по ВРЕМЕНИ и СКОРОСТИ: отправитель пишет событие сейчас, обработчик читает его, когда готов. Между ними буфер, который сглаживает пики нагрузки и изолирует сбои — упавший consumer не валит продюсера, а его работу подхватят при восстановлении.
Полезно различать роли сообщений. Событие (event) — это факт, который произошёл («OrderPaid»): несколько подписчиков могут реагировать каждый по-своему. Команда (command) — это намерение для одного исполнителя («ChargeCard»): обычно идёт на одного consumer'а. Брокеры умеют оба сценария, но дизайн потоков разный.
Queue против pub/sub
Две базовые модели. Queue (work queue) — одно сообщение получает ОДИН consumer из группы, остальные конкурируют за следующие; так распределяют задачи. Pub/sub — каждое логическое подписавшееся приложение получает свою копию потока и обрабатывает независимо.
В Kafka обе модели реализуются через consumer groups: одна группа конкурирует за партиции (work queue), а разные группы получают свои независимые копии потока (pub/sub). У классических брокеров (RabbitMQ) очереди явные, а pub/sub строится через exchange и bindings.

Три семантики доставки
At-most-once — сообщение может потеряться, но дубликатов не будет (commit/ack ДО обработки). At-least-once — потерь нет, но возможны дубли (обработка → commit/ack; падение между ними даёт повтор). Exactly-once — без потерь и без дублей, обычно в рамках одной системы и ценой сложности.
На практике дефолт — at-least-once, и поэтому критическая роль отдаётся идемпотентности обработки. End-to-end exactly-once в распределённой системе с внешними побочными эффектами почти всегда строят как at-least-once + идемпотентность, а не как «настоящий» exactly-once брокера.
Идемпотентность обработки
Идемпотентная обработка — это когда повторная доставка того же сообщения даёт ТОТ ЖЕ эффект, что и первая. Без неё at-least-once приводит к дублям заказов/платежей/писем.
Реализуют через идемпотентный ключ: сохраняют processed_id в БД и при повторе пропускают обработку, либо строят логику как upsert/CAS вместо «вставить новое». Ключ должен быть стабильным и приходить с сообщением, чтобы переживать любые ретраи и rebalance.
Порядок и масштабирование
Глобальный порядок по всему потоку требует одного потока обработки — это не масштабируется. Поэтому брокеры дают порядок не глобально, а в пределах ключа: события одной сущности (id заказа, аккаунта) идут в одну партицию/очередь и обрабатываются последовательно, а разные ключи параллелятся.
Это компромисс: чтобы получить параллелизм, отказываемся от глобального порядка. Если бизнес требует строгой последовательности — выделяют ключ агрегата и платят за это масштабом per-ключ, а не за весь поток.
БД + событие: transactional outbox
Типичная задача — атомарно записать в БД и отправить событие в брокер. Двухфазный коммит (2PC) дорог и хрупок, его избегают.
Стандартное решение — transactional outbox: в одной транзакции БД пишут и бизнес-данные, и запись в таблицу outbox; отдельный фоновой relay (или CDC поверх WAL) читает outbox и отправляет в брокер. Если упасть между БД и брокером, relay перешлёт позже — а consumer переживёт повтор благодаря идемпотентности.
Как отвечать на собеседовании
Объясните роль брокера (асинхронность, буфер, изоляция сбоев) и различите event vs command. Сравните queue и pub/sub (в Kafka — через consumer groups). Перечислите три семантики и подчеркните: дефолт at-least-once + идемпотентность вместо «настоящего» exactly-once. Назовите компромисс порядок/масштаб (порядок по ключу) и transactional outbox как стандартный паттерн «БД + событие» вместо 2PC.