• Русский
  • Настройка dead-letter exchanges и retry queues

    Используйте dead-letter exchanges (DLX) для перемещения сообщений из основного пути обработки после событий negative acknowledgement, expiration или переполнения очереди. Сочетайте DLX с retry queues, когда рабочей нагрузке требуется отложить вторую или последующую попытку доставки.

    Это руководство разделяет две задачи:

    • dead-letter queue (DLQ) для сообщений, которые должны прекратить обычную обработку.
    • retry queue для сообщений, которые должны вернуться в рабочую очередь после задержки.

    Применимые сценарии

    Используйте этот шаблон, если вам нужно одно или несколько из следующего:

    • Помещать навсегда неудачные сообщения в DLQ для анализа.
    • Откладывать повторные попытки вместо немедленного повторного помещения сообщения в очередь.
    • Держать основную очередь свободной от сообщений, которые repeatedly fail.

    Не полагайтесь на бесконечные циклы basic.nack или basic.reject с requeue=true. Они создают тесные циклы повторной доставки и усложняют контроль состояния очереди.

    Процедура

    В этом примере:

    • Producers публикуют рабочие сообщения в orders.work.
    • Consumers читают из orders.q.
    • Постоянные ошибки отправляются в orders.dlq через orders.dlx.
    • Временные ошибки повторно публикуются приложением в orders.retry.
    • orders.retry.30s хранит повторно отправленные сообщения 30 секунд, а затем отправляет их обратно в orders.work через dead-lettering.

    1. Объявите exchanges

    rabbitmqadmin \
      --host <management-host> \
      --port 15672 \
      --username <admin-user> \
      --password <admin-password> \
      --vhost / \
      declare exchange name=orders.work type=direct durable=true
    
    rabbitmqadmin \
      --host <management-host> \
      --port 15672 \
      --username <admin-user> \
      --password <admin-password> \
      --vhost / \
      declare exchange name=orders.retry type=direct durable=true
    
    rabbitmqadmin \
      --host <management-host> \
      --port 15672 \
      --username <admin-user> \
      --password <admin-password> \
      --vhost / \
      declare exchange name=orders.dlx type=direct durable=true

    2. Объявите рабочую очередь и DLQ

    Рабочая очередь отправляет failed messages в orders.dlx с routing key orders.failed:

    rabbitmqadmin \
      --host <management-host> \
      --port 15672 \
      --username <admin-user> \
      --password <admin-password> \
      --vhost / \
      declare queue \
      name=orders.q \
      durable=true \
      arguments='{"x-dead-letter-exchange":"orders.dlx","x-dead-letter-routing-key":"orders.failed"}'
    
    rabbitmqadmin \
      --host <management-host> \
      --port 15672 \
      --username <admin-user> \
      --password <admin-password> \
      --vhost / \
      declare queue name=orders.dlq durable=true

    Свяжите очереди:

    rabbitmqadmin \
      --host <management-host> \
      --port 15672 \
      --username <admin-user> \
      --password <admin-password> \
      --vhost / \
      declare binding \
      source=orders.work \
      destination_type=queue \
      destination=orders.q \
      routing_key=orders
    
    rabbitmqadmin \
      --host <management-host> \
      --port 15672 \
      --username <admin-user> \
      --password <admin-password> \
      --vhost / \
      declare binding \
      source=orders.dlx \
      destination_type=queue \
      destination=orders.dlq \
      routing_key=orders.failed

    Когда consumer отклоняет сообщение из orders.q с requeue=false, RabbitMQ отправляет сообщение в orders.dlq через dead-lettering.

    3. Объявите retry queue

    Создайте retry queue, которая задерживает повторную доставку на 30 секунд:

    rabbitmqadmin \
      --host <management-host> \
      --port 15672 \
      --username <admin-user> \
      --password <admin-password> \
      --vhost / \
      declare queue \
      name=orders.retry.30s \
      durable=true \
      arguments='{"x-message-ttl":30000,"x-dead-letter-exchange":"orders.work","x-dead-letter-routing-key":"orders"}'

    Свяжите retry queue:

    rabbitmqadmin \
      --host <management-host> \
      --port 15672 \
      --username <admin-user> \
      --password <admin-password> \
      --vhost / \
      declare binding \
      source=orders.retry \
      destination_type=queue \
      destination=orders.retry.30s \
      routing_key=orders.30s

    4. Реализуйте решение приложения о повторной попытке

    Используйте логику приложения, чтобы определить, является ли сбой временным или постоянным:

    • Для временных сбоев публикуйте сообщение в orders.retry с routing key orders.30s.
    • Для постоянных сбоев отклоняйте сообщение из orders.q с requeue=false либо явно публикуйте его в путь DLQ.

    Когда consumer повторно публикует сообщение при временном сбое в orders.retry, он должен подтверждать исходную доставку только после успешной публикации retry. В production используйте publisher confirms для пути повторной публикации, чтобы consumer не удалял исходное сообщение до того, как RabbitMQ примет копию для retry.

    Если публикация retry завершается сбоем или confirm не получен, не подтверждайте исходную доставку положительно. Повторно поместите сообщение в очередь или повторите попытку в соответствии с вашей политикой обработки ошибок.

    Даже при использовании publisher confirms дубликаты по-прежнему возможны во время reconnects или частичных сбоев. Consumers и downstream processors должны оставаться idempotent.

    RabbitMQ не предоставляет универсальный встроенный счетчик повторных попыток для classic queues. Если вам нужен максимальный лимит повторных попыток, используйте логику приложения, проверяйте заголовок x-death или при необходимости используйте delivery limits quorum queue.

    5. Проверьте топологию

    Проверьте exchanges, queues и bindings:

    rabbitmqadmin \
      --host <management-host> \
      --port 15672 \
      --username <admin-user> \
      --password <admin-password> \
      --vhost / \
      list exchanges name type
    
    rabbitmqadmin \
      --host <management-host> \
      --port 15672 \
      --username <admin-user> \
      --password <admin-password> \
      --vhost / \
      list queues name arguments messages consumers
    
    rabbitmqadmin \
      --host <management-host> \
      --port 15672 \
      --username <admin-user> \
      --password <admin-password> \
      --vhost / \
      list bindings source_name destination_name routing_key

    Рекомендуемые практики

    • Используйте dedicated DLQ для анализа и повторного воспроизведения.
    • Используйте delayed retry queues вместо немедленных циклов повторного помещения в очередь.
    • Делайте consumers idempotent, поскольку retries и failovers могут создавать дублирующиеся доставки.
    • Определяйте в приложении, когда сообщение должно прекратить повторные попытки.
    • Отслеживайте рост DLQ. Растущая DLQ обычно указывает на проблему в коде, схеме или зависимости.

    Связанная информация