Этот вопрос проверяет понимание семантики доставки сообщений и того, как брокер ведет себя при падении потребителя до ack.
Если воркер получил сообщение и упал до подтверждения, сообщение не считается обработанным. В RabbitMQ оно останется в состоянии unacked (если уже было доставлено) и после разрыва соединения/канала будет переотправлено другому воркеру или тому же воркеру после восстановления. Это означает модель доставки “как минимум один раз” (at-least-once), поэтому возможны дубликаты обработки. Чтобы избежать повторных эффектов, обработку делают идемпотентной и подтверждают сообщение только после успешного завершения.
Падение воркера до подтверждения почти всегда означает, что брокер будет считать сообщение не обработанным и попытается доставить его заново.
Доставка сообщения потребителю переводит его в одно из состояний:
ready — лежит в очереди и ждет потребителя
unacked — доставлено потребителю и ожидает подтверждения (ack) или отказа (nack/reject)
Если воркер упал до ack:
брокер обнаружит закрытие TCP/AMQP-соединения или канала
все unacked сообщения этого канала будут возвращены в очередь и станут кандидатами на повторную доставку (redelivery)
Следствие:
гарантия обычно at-least-once
то же сообщение может прийти повторно (в т.ч. другому воркеру)
Возможны дубликаты
повторная доставка может случиться не только при падении, но и при сетевых сбоях, таймаутах, рестарте воркера.
Нельзя “надеяться”, что сообщение обработается ровно один раз
“ровно один раз” (exactly-once) в реальных системах достигается не настройкой RabbitMQ, а схемой обработки (идемпотентность + дедупликация + транзакционность на стороне хранилища).
Подтверждать только после успешной обработки
ack ставят в самом конце, когда все побочные эффекты выполнены.
Делать обработку идемпотентной
сохранять message_id/ключ операции и не выполнять эффект повторно.
Ограничивать “в полете” (prefetch)
уменьшает число unacked сообщений, которые “откатятся” при падении.
def on_message(ch, method, properties, body):
try:
# 1) обработка
process(body) # бизнес-логика
# 2) подтверждаем только после успеха
ch.basic_ack(delivery_tag=method.delivery_tag)
except Exception:
# можно отправить в DLQ или сделать повторную доставку
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True)
Вывод
Если воркер падает до ack, сообщение почти наверняка будет доставлено повторно, поэтому надежная система строится вокруг ручных подтверждений, prefetch и идемпотентной обработки.