Гарантирует ли режим подтверждения получения сообщения в RabbitMQ доставку ровно один раз и почему?
Что проверяют
Знаете ли вы, какие гарантии доставки реально даёт брокер и где заканчивается его ответственность. Правильный короткий ответ: нет, подтверждения дают «хотя бы один раз», а не «ровно один». Следом спрашивают, как тогда защититься от дублей в своём сервисе.
Как отвечать
Подтверждения бывают двух видов, и их стоит развести:
- consumer acks: потребитель подтверждает обработку. С
autoAck=trueсообщение считается доставленным в момент отправки потребителю, это «не больше одного раза»: упал обработчик, сообщение потеряно. С ручнымbasicAckполучаем «хотя бы один раз»; - publisher confirms: брокер подтверждает продюсеру, что принял сообщение. Не дождавшись ответа, продюсер шлёт повторно, и в очереди может оказаться дубль.
Откуда дубли у потребителя: сообщение обработано, запись в базу прошла, а ack не дошёл из-за обрыва соединения. Брокер вернёт сообщение в очередь и доставит снова с флагом redelivered. Тот же эффект, если потребитель не подтвердил сообщение за consumer_timeout (по умолчанию 30 минут): канал закрывается, неподтверждённые сообщения уходят на повтор.
Чтобы не терять сообщения: устойчивая очередь (сейчас для этого рекомендуют quorum-очереди), persistent-сообщения, publisher confirms и ручные acks. Чтобы не обрабатывать дважды: идемпотентный потребитель. Обычно это таблица обработанных messageId с уникальным ключом, куда запись идёт в одной транзакции с бизнес-изменением, или естественная идемпотентность операции вроде upsert по ключу.
В Spring AMQP режим AcknowledgeMode.AUTO означает, что контейнер подтверждает сообщение после успешного выхода из метода @RabbitListener, а при исключении по умолчанию возвращает его в очередь. Это не то же самое, что autoAck в самом клиенте.
Где спотыкаются
Говорят «с ручными ack будет ровно один раз». Ручное подтверждение закрывает только потерю, дубли остаются.
Путают AcknowledgeMode.AUTO в Spring с autoAck=true и думают, что сообщения подтверждаются до обработки.
Бесконечно возвращают «ядовитое» сообщение в очередь через requeue=true. Нужен dead letter exchange или лимит повторов x-delivery-limit у quorum-очереди.