Очереди сообщений
Пока компоненты вызывают друг друга напрямую, всё просто — вызов либо вернул результат, либо упал. Очереди и события начинаются там, где этой простоты больше нет: producer кладёт сообщение в брокер и идёт дальше, не зная, кто и когда его прочитает, а между записью в БД и публикацией события успевает случиться падение процесса. Здесь интервьюер проверяет не знание модного слова, а способность рассуждать о согласованности и доставке под асинхронностью.
Главная ловушка темы — переносить интуицию синхронного вызова на брокер. В монолите запись и публикация атомарны, потому что это один вызов; разнесите их по двум системам — и появляется dual-write, частичный сбой и лишь итоговая согласованность. Вторая ловушка — вера в «бесплатные» гарантии: что брокер сам обеспечит exactly-once, что повтор доставки можно игнорировать, что dead-letter queue — это урна для плохих сообщений. Не так: дубликаты обезвреживает только идемпотентный потребитель, а DLQ — буфер для разбора. Эта тема разбирает обмен сообщениями по слоям — от стиля общения компонентов до того, как именно сообщение доходит ровно один раз по эффекту.
Карта темы
- Событийно-ориентированная архитектура — компоненты общаются через брокер событиями, а не прямыми вызовами; что это даёт и чем расплачивается.
- Брокер сообщений — асинхронная развязка producer и consumer; сущности
RabbitMQ: exchange, queue, binding и контраст сKafka-логом. - Гарантии доставки сообщений — at-most-once, at-least-once и effectively-once, и почему идемпотентность обязательна при повторах.
- Группы потребителей Kafka — одна партиция на потребителя в группе, простой лишних, ребаланс и коммит offset при сбое.
- Порядок сообщений в Kafka — гарантия порядка только внутри партиции и ключ партиции для связанных событий.
- Паттерн outbox — как одна локальная транзакция решает проблему dual-write, а relay публикует события уже после коммита.
- Dead-letter queue — боковая очередь для «отравленных» сообщений, разбор и переигрывание с идемпотентными потребителями.
- Fan-out уведомлений — событие в durable-брокер и веер per-channel доставок с независимыми повторами и дедупликацией.
- Очередь задач на Postgres —
SELECT ... FOR UPDATE SKIP LOCKED, lease-таймаут и dead-letter для надёжных фоновых задач без отдельного брокера.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
| Считать событийно-ориентированную систему сильно согласованной | Ложные ожидания — по природе она лишь итогово согласована |
| Думать, что события — это просто логирование | Упустить, что событие и есть реальный канал общения компонентов |
| Считать, что producer должен знать список потребителей | Теряется развязка — добавить потребителя нельзя без правки producer |
| Менять местами at-least-once и at-most-once | at-least-once задваивает, at-most-once теряет — не наоборот |
| Считать, что брокер даёт сквозной exactly-once бесплатно | Дубликаты дойдут до потребителя без его дедупликации |
Слать ACK до обработки сообщения | Падение посреди обработчика теряет сообщение — брокер не повторит |
Сравнивать offset сообщений из разных партиций | Между партициями порядка нет — это независимые шкалы |
| Поднимать потребителей больше числа партиций | Лишние простаивают — параллелизм ограничен числом партиций |
| Считать, что relay из outbox публикует внутри той же транзакции | Неверная модель — relay работает отдельно, после коммита |
| Делать dual-write — записать в БД и тут же опубликовать событие | Падение между шагами теряет событие навсегда |
| Считать DLQ способом «выкинуть» плохое сообщение | Забытая DLQ — молчаливая потеря данных под видом обработки |
| Забыть про идемпотентность обработчика при at-least-once | Повторная доставка даёт второй эффект — задвоенный платёж |
Значение для собеседований
Очереди и события — обязательная тема на senior-уровне Go-интервью, и спрашивают не схему на доске, а понимание доставки и согласованности. Интервьюер проверяет, переносите ли вы вслепую интуицию монолита на распределённую систему.
Что обычно проверяют:
- Чем событийно-ориентированная архитектура платит за слабую связанность — асинхронность, сложная отладка, лишь итоговая согласованность.
- Чем
RabbitMQотличается отKafka— умный брокер с маршрутизацией против партиционированного лога с offset на стороне потребителя. - Чем at-least-once отличается от at-most-once и почему at-least-once безопасен лишь с идемпотентным обработчиком.
- Как
Kafkaгарантирует порядок — только внутри партиции, и зачем ключ партиции для связанных событий. - Что такое проблема dual-write и как паттерн outbox решает её одной локальной транзакцией плюс relay после коммита.
- Зачем нужна dead-letter queue и почему она требует инспекции, а не молчаливого сброса.
- Как fan-out на durable-брокере даёт независимые повторы по каналам и effectively-once через дедупликацию.
Типичный неверный ответ: «брокер сам гарантирует exactly-once, потребителю ничего делать не надо». Это запускает разбор того, что сквозной exactly-once в распределённой системе практически недостижим и приближается как effectively-once — at-least-once плюс дедупликация по idempotency key на стороне потребителя.