Простейший вариант обработки сообщений из очереди — слать ack после успешной обработки и nack с requeue=true при временной неудаче. Однако такой подход работает только для временных/разовых ошибок, при серьезной проблеме потребитель будет пытаться обработать сообщение до посинения с маленьким интервалом между попытками.

Чтобы этого избежать, можно создать для таких сообщений специальную очередь — dead letter queue (dlq) и для серьезных ошибок слать nack с requeue=false. А чтобы сделать несколько попыток, для каждого сообщения в dlq можно назначить TTL, и установить в качестве dlq для dlq оригинальную очередь. Тогда при истечении TTL сообщение из DLQ попадает обратно в оригинальную очередь с заголовком, содержащим число “смертей”. Потребитель может проверять это число и, если оно превысило предел, не обрабатывать сообщение совсем, но послать ack. Таким образом, TTL для dlq служит интервалом между попытками.

Если в сообщениях есть ценная информация и выкидывать их совсем нежелательно, то при превышении числа попыток потребитель может их складывать в третью очередь (parking lot) для последующего ручного исправления.

С точки зрения настроек RabbitMQ нужно добавить к параметрам оригинальной очереди

x-dead-letter-exchange = ""
x-dead-letter-routing-key = "dlq_queue_name"

а к параметрам dlq

x-dead-letter-exchange = ""
x-dead-letter-routing-key = "original_queue_name"
x-message-ttl = 100500 # milliseconds

В обоих случаях используется стандартный обменник, чтобы перенаправлять сообщения напрямую. TTL можно еще устанавливать для каждого сообщения индивидуально (если хочется иметь экспоненциально увеличивающуюся задержку), но это придется делать уже на стороне потребителя.

Разумеется, возможны и другие сценарии (с общей dlq для всего, например), но описанный — один из самых простых.