Перейти к основному содержимому
Нормативная версия документации - английская

Эта страница переведена с английского языковой моделью. При любом расхождении верен английский оригинал.

Подтверждение и переобработка

Чем ack, drop, retry и retry_after становятся в RabbitMQ, где каждое подтверждение - кадр в канале доставки.

ИсходRabbitQueue (классическая)RabbitQuorumQueue
ackbasic.ack: сообщение уходит из очередиТо же
dropbasic.reject без requeue: сообщение переносится в обменник недоставленных сообщений, если он есть у очереди, иначе отбрасываетсяТо же
retry()basic.reject с requeue: сообщение возвращается в очередь, счёт не ведётсяbasic.reject с requeue: сообщение возвращается в очередь, сервер учитывает доставку
retry() при max_attempts и dead_letterРантайм публикует копию со счётчиком, см. нижеСобственный предел доставок сервера, см. ниже
retry_after(delay)Копия, которая ждёт, см. нижеТо же при заданном delay; без него немедленный возврат в очередь

Крейт никогда не отправляет basic.nack. Для одной доставки он делает то же, что basic.reject, а начиная с RabbitMQ 4.3 к пределу доставок кворумной очереди засчитывается только отклонение. Исход обработчика пакетов подтверждает каждую доставку пакета одним и тем же кадром. Ответ, который приходит через запрашивающую сторону, подтверждать не нужно: его получает потребитель без подтверждений.

Переобработка​

Переобработка при max_attempts и dead_letter: классическая очередь оставляет счёт рантайму, кворумная очередь считает на сервереОбработчик возвращает retry()при max_attempts и dead_letterRabbitQueue:рантайм считает вx-ruststream-retry-countRabbitQuorumQueue:сервер считает вx-delivery-countНижепредела:копияв очередьНа пределе:копияв DLQНижепредела:отклонено,в очередьСверхпредела:сервершлёт в DLQСнова в очередидля новой доставкиБез обоих объявлений retry() - это basic.rejectс requeue, у обоих типов очередей
Схема текстом
  1. Обработчик возвращает retry() при max_attempts и dead_letter → RabbitQueue: рантайм считает в x-ruststream-retry-count
  2. Обработчик возвращает retry() при max_attempts и dead_letter → RabbitQuorumQueue: сервер считает в x-delivery-count
  3. RabbitQueue: рантайм считает в x-ruststream-retry-count → Ниже предела: копия в очередь: ниже
  4. RabbitQueue: рантайм считает в x-ruststream-retry-count → На пределе: копия в DLQ
  5. RabbitQuorumQueue: сервер считает в x-delivery-count → Ниже предела: отклонено, в очередь: ниже
  6. RabbitQuorumQueue: сервер считает в x-delivery-count → Сверх предела: сервер шлёт в DLQ
  7. Ниже предела: копия в очередь → Снова в очереди для новой доставки
  8. Ниже предела: отклонено, в очередь → Снова в очереди для новой доставки
  9. Без обоих объявлений retry() - это basic.reject с requeue, у обоих типов очередей

max_attempts(n) и dead_letter(name) в точке монтирования прекращают обработку сообщения, которое раз за разом не удаётся обработать. Первая доставка считается первой попыткой. Кто ведёт счёт, зависит от типа очереди.

Классическая очередь ничего не считает, поэтому счёт ведёт рантайм. При переобработке ниже предела он отклоняет оригинал без requeue и публикует копию в обменник по умолчанию под именем очереди, и копия попадает в конец той же очереди. Копия хранит номер попытки в заголовке x-ruststream-retry-count. На пределе копия уходит в очередь, названную в dead_letter, а без dead_letter доставка отклоняется, и ничего не публикуется. Если у классической очереди есть собственный обменник недоставленных сообщений, отклонение каждого оригинала попадает и в него.

Кворумная очередь сама считает свои доставки. Пара объявлений становится аргументом очереди x-delivery-limit (max_attempts минус один, потому что сервер считает возвраты) и её маршрутом недоставленных сообщений в очередь dead_letter. Переобработка - это обычный basic.reject с requeue, а сервер переносит сообщение в очередь недоставленных сообщений, когда оно превышает предел, независимо от того, работает ли сервис. Аргументы попадают в очередь, только если её объявляет этот сервис, поэтому паре нужен declare_topology(true), а без него запуск прерывается. Кворумная очередь не публикует копий, поэтому издатель переобработки (out_retry) на ней не компилируется.

Отложенная переобработка​

Отложенная переобработка ждёт в процессе сервиса, в очереди ожидания с TTL или в обменнике delayed-message, и каждый из них возвращает копию в очередьОбработчик возвращаетretry_after(delay)Без delay:таймер впроцессесервисаDelay::dlx_ttl:<queue>.retryTTL у каждогосообщенияDelay::plugin_dme:x-delayв плагинеОчередь, откуда пришла доставка;копия - новое сообщениеТаймер теряется при аварийном завершении процесса;копия на брокере переживает сервис
Схема текстом
  1. Обработчик возвращает retry_after(delay) → Без delay: таймер в процессе сервиса
  2. Обработчик возвращает retry_after(delay) → Delay::dlx_ttl: <queue>.retry TTL у каждого сообщения
  3. Обработчик возвращает retry_after(delay) → Delay:: plugin_dme: x-delay в плагине
  4. Без delay: таймер в процессе сервиса → Очередь, откуда пришла доставка; копия - новое сообщение: опубликована снова
  5. Delay::dlx_ttl: <queue>.retry TTL у каждого сообщения → Очередь, откуда пришла доставка; копия - новое сообщение: выпущена
  6. Delay:: plugin_dme: x-delay в плагине → Очередь, откуда пришла доставка; копия - новое сообщение: выпущена
  7. Таймер теряется при аварийном завершении процесса; копия на брокере переживает сервис

retry_after(delay) запрашивает новую доставку не раньше, чем через delay. В AMQP нет задержки для отдельного сообщения, поэтому ожидание происходит в одном из трёх мест.

Если у классической очереди нет delay, копию держит рантайм. Он отклоняет оригинал и публикует копию, когда срабатывает таймер в сервисе. Штатное завершение работы дожидается таймера, а процесс, который завершился аварийно до срабатывания, теряет копию. У кворумной очереди без delay рантайму нечего держать: доставка сразу возвращается в очередь, а предупреждение в журнале сообщает, что задержка отброшена.

Delay::dlx_ttl хранит копию на брокере. Крейт публикует её в очередь ожидания, по умолчанию <queue>.retry, со сроком действия сообщения, равным задержке, и после неё подтверждает оригинал в том же канале. Когда срок истекает, очередь ожидания переносит копию как недоставленное сообщение обратно в исходную очередь. Очередь выпускает просроченные сообщения только из своего начала, поэтому короткая задержка за длинной ждёт длинную. Отдельная очередь ожидания для каждой задержки разводит их.

Delay::plugin_dme под фичей plugin-dme публикует копию в обменник плагина delayed-message, по умолчанию <queue>.delay, с задержкой в заголовке x-delay. Плагин направляет копию в исходную очередь, когда задержка прошла, и задержки разной длины не ждут друг друга.

Обе цели - это инфраструктура. При declare_topology(true) каждая подписка объявляет свою. Без объявления подписка при запуске проверяет, что цель существует, и запуск прерывается, если её нет, потому что сервер отбросит копию, отправленную в несуществующую очередь ожидания. Копия - новое сообщение, поэтому сервер считает её доставки с нуля. В ней заголовок x-ruststream-retry-count увеличен на единицу, и на классической очереди рантайм проверяет по нему предел до отправки копии: доставка на пределе уходит в очередь недоставленных сообщений, а не ждёт снова.

Правила на стороне сервера определяет RabbitMQ (отрицательные подтверждения, предел доставок кворумной очереди, недоставленные сообщения, TTL сообщения, обменник delayed-message).