Подтверждение доставки ≠ бизнес-результат
- 01ПубликацияПродюсер → брокер
- 02ХранениеНадёжность записи
- 03ОбработкаБизнес-изменение
- 04ПодтверждениеВозможен повтор
У брокера сообщений нет переключателя reliable=true. Надёжность складывается из настроек продюсера, хранения в брокере, подтверждений потребителя и того, как приложение фиксирует бизнес-результат.
Ниже — обновлённый разбор RabbitMQ, ActiveMQ Classic и Kafka без обещаний универсального «ровно один раз».
Сначала разделим модели
RabbitMQ маршрутизирует сообщения через exchanges в очереди. Сообщение из очереди обычно обрабатывает один из конкурирующих потребителей. Если одно событие должны получить несколько систем, для них создают отдельные очереди и привязывают к exchange.
ActiveMQ Classic вырос вокруг JMS и поддерживает очереди, темы, постоянные подписки и несколько протоколов, включая OpenWire и AMQP. Конкретное поведение зависит от режима сохранения, типа сессии и подтверждений.
Kafka хранит записи в партиционированном логе. Чтение не удаляет запись: потребитель хранит позицию — offset — и при необходимости может перечитать историю. В классической consumer group одна партиция назначена одному потребителю в каждый момент; это не описание отдельного механизма share groups.
Отсюда первое правило: очередь задач, pub/sub и журнал событий похожи только на схеме верхнего уровня. У них разные способы маршрутизации, повторного чтения и масштабирования.
Что означают гарантии доставки
- At-most-once. Сообщение может потеряться, зато повторная обработка не планируется. Так бывает, если подтвердить получение до завершения работы или использовать автоматическое подтверждение без возможности восстановления.
- At-least-once. При сбое сообщение может быть доставлено повторно. Это обычный выбор для важных событий, но потребитель обязан переживать дубли.
- Exactly-once. Это не свойство одного сетевого вызова. Нужна общая транзакционная граница между чтением, бизнес-эффектом и фиксацией позиции либо дедупликация на стороне получателя.
At-least-once действует в согласованной модели отказов и хранения. TTL, retention, удаление очереди, переполнение и политика DLQ могут прервать путь до успешной бизнес-обработки. Повторная доставка не означает бесконечную сохранность любого сообщения.
Фраза «брокер поддерживает exactly-once» без указания границ почти ничего не говорит. Важно уточнить: между какими компонентами, для каких операций и что произойдёт с внешней базой или API.
RabbitMQ: подтверждение не отменяет дубли
У RabbitMQ есть два независимых механизма:
- Publisher confirms сообщают продюсеру, что брокер обработал публикацию. Это не подтверждение обработки потребителем и не самостоятельная гарантия, что сообщение попало в нужную очередь.
- Consumer acknowledgements сообщают брокеру, что доставку можно удалить из очереди.
Для at-least-once потребитель отправляет ack только после успешного бизнес-действия. Если он записал результат в базу, но потерял соединение до ack, RabbitMQ вернёт сообщение в очередь. Следующая обработка увидит дубль — это нормальный сценарий, а не поломка брокера.
Успешный confirm возможен и для сообщения, которому exchange не нашёл ни одной очереди. Чтобы замечать такие случаи, для AMQP 0-9-1 используют mandatory и обрабатывают basic.return. Ожидание confirm без проверки маршрутизации может оставить продюсера довольным, а очередь — пустой.
Поэтому потребителю нужна идемпотентная операция или сохранённый идентификатор уже обработанного события. Флаг redelivered полезен для диагностики, но не заменяет корректную логику обработки.
Durable-очередь, persistent-сообщение, репликация и confirms закрывают разные участки пути. Их нельзя заменить одной галочкой.
ActiveMQ Classic: те же вопросы, другой стек
В ActiveMQ Classic надёжность зависит от persistent delivery, режима acknowledgement и транзакционной JMS-сессии. При rollback, разрыве соединения или незавершённой транзакции сообщение может быть доставлено повторно; после лимита попыток его обычно направляют в DLQ.
Транзакция внутри брокера полезна, но не делает атомарной запись в произвольную внешнюю систему. Если обработчик меняет отдельную базу данных, границу согласованности всё равно нужно проектировать явно.
Kafka: порядок только внутри партиции
Kafka сохраняет порядок записей внутри одной партиции, а не во всём топике. Если события одного клиента должны идти последовательно, их обычно отправляют с одинаковым ключом, чтобы они попадали в одну партицию.
Одинаковый ключ предполагает согласованную стратегию распределения по партициям. Увеличение их числа может изменить маршрут ключа. Кроме того, порядок записей в логе не гарантирует порядок завершения работы, если потребитель обрабатывает их параллельно.
Момент фиксации offset определяет поведение при сбое:
- offset зафиксирован до бизнес-операции — возможна потеря обработки;
- бизнес-операция выполнена до фиксации offset — возможен повтор;
- результат и offset сохранены атомарно в одной системе — появляется более сильная гарантия в этой границе.
Идемпотентный producer Kafka защищает от части дублей при повторной отправке в Kafka. Транзакции Kafka позволяют атомарно записать выходные события и offsets в сценарии Kafka → обработка → Kafka. Для такого pipeline выходные записи и offsets должны фиксироваться одной транзакцией, автоматический commit offsets отключается, а следующий читатель использует isolation.level=read_committed, чтобы не видеть незавершённые и отменённые транзакции. Нужно также обработать abort, восстановление позиции и смену владельца партиции. Побочный эффект во внешней БД, платёжном шлюзе или HTTP API в эту транзакцию автоматически не входит.
Как выбирать брокер
Смотреть стоит не на таблицу «кто быстрее», а на задачу:
- RabbitMQ удобен для очередей работ, сложной маршрутизации и независимых очередей подписчиков;
- ActiveMQ Classic логичен в системах с JMS и уже сложившейся Java-интеграцией;
- Kafka подходит, когда важны длительное хранение, повторное чтение, потоковая обработка и масштабирование по партициям.
Перед выбором я фиксирую семь вопросов:
- Можно ли потерять сообщение?
- Можно ли обработать его повторно?
- Как потребитель распознаёт дубль?
- Где подтверждается публикация, а где обработка?
- Нужен ли порядок и для какого ключа?
- Что делать с ошибочным сообщением после нескольких попыток?
- Как воспроизвести поток и проверить результат после инцидента?
Если на эти вопросы нет ответов, название брокера архитектуру не спасёт.
Официальная документация
- RabbitMQ Reliability Guide
- RabbitMQ: Consumer Acknowledgements and Publisher Confirms
- Apache ActiveMQ Classic: Message Redelivery and DLQ Handling
- Apache Kafka: Design and Message Delivery Semantics
Из моего Telegram-канала @it_underside.
Ваш ДПУПП
Похожие статьи
RAG для компании: от поиска по документам до полезного ассистента
RAG простыми словами: как устроены поиск и индексация, зачем они нужны рядом с LLM, чем отличаются решения для небольшой команды и enterprise и как получить полезного ассистента.
Форматы обмена данными: JSON, XML, Protobuf, Avro и MessagePack
Как выбирать формат не по моде, а по контракту: эволюция схем, обратная и прямая совместимость, инструменты и измеренная стоимость обработки.
Сделать аналог — только начало: считаем TCO решения
Как сравнивать продукт, форк, совместимую реализацию и собственную разработку по полной стоимости владения: обновления, эксплуатация, компетенции, риски и выход.