infra
November 8, 2021

Вы хочете песен? Их есть у меня! (Poison Message #2)

Самое время рассмотреть “достаточно хороший” алгоритм для борьбы с Poison Message. Здесь будет уже специфика RabbitMQ и к Apache Kafka она не применима, точнее применима только частично - но это уже совсем другая история.

В первой части мы разобрали несколько примеров и сформулировали проблему Poison Message, здесь же рассмотрим сам алгоритм её решения.

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

Сам RabbitMQ не умеет отслеживать число передоставок, а только использует флаг redelivered. Таким образом, со стороны сервиса мы можем узнать только что это сообщение повторное, но вот повторяется оно второй раз или 5439й - никак. Кроме того RabbitMQ возвращает сообщение в голову очереди, тем самым блокируя получение следующих за ним сообщений и дальнейшую работу сервиса. Решением является ручная публикация сообщение в конец очереди и ручное же выставление собственного заголовка "x-delivery-count".

Алгоритм

Сценарий 1 - корректная работа

Так выглядит штатная обработка сообщения

  1. Поставщик кладёт сообщение в очередь
  2. Сервис читает сообщение
  3. Сервис обрабатывает сообщение
  4. Сервис подтверждает обработку сообщения (ack)
  5. Брокер удаляет сообщение из очереди

Сценарий 2 - Poison message

Когда мы точно знаем что есть проблема - и пытаемся её решить несколько раз

  1. Поставщик кладёт сообщение в очередь
  2. Сервис читает сообщение
  3. Сервис проверяет заголовок x-delivery-count (чтобы был не больше чем max retries)
  4. Сервису не удается обработать сообщение, но он не завершается
  5. Сервис создает копию сообщения и увеличивает значение x-delivery-count на 1
  6. Сервис публикует сообщение в конец очереди и подтверждает (ack) обработку исходного сообщения
  7. Брокер удаляет исходное сообщение из очереди
  8. Шаги 2-7 повторяются max retries раз
  9. Сервис читает сообщение
  10. Сервис отклоняет сообщение (reject, requeue = false)
  11. Брокер помечает сообщение флагом, x-death
  12. Брокер отправляет сообщение в dead-letter-exchange
Повторяем несколько раз через конец очереди, а затем "выбрасываем"

Сценарий 3 - Аварийное завершение сервиса

Тут начинается сценарий определения Poison Message

  1. Поставщик кладёт сообщение в очередь
  2. Сервис читает сообщение
  3. Сервис аварийно завершается ☠
  4. Брокер обнаруживает разрыв и ставит сообщение на переотправку
    Сервис перезапускается
  5. Сервис читает сообщение
  6. Сервис обнаруживает заголовок redelivered=true
    Сервис создает копию сообщения и увеличивает значение x-delivery-count на 1
  7. Сервис публикует сообщение в конец очереди и подтверждает (ack) обработку исходного сообщения
  8. Брокер удаляет исходное сообщение из очереди
  9. Далее события развиваются по Сценарию 1 или Сценарию 2

⚠ ВНИМАНИЕ ⚠
Если сервис обнаруживает redelivered=true, то он даже не пытается провести обработку сообщения, поскольку это вновь может привести к падению без возможности разорвать цикл перезапусков. Вместо этого сервис сразу публикует сообщение в конец очереди и запускает Сценарий 2.

Публиковать в отдельную очередь или использовать Dead Letter Exchange - это уже дело вкуса, но вот рассмотреть ограничения стоит внимательно:

  • при таком подходе нарушается порядок следования сообщений. Если у вас есть жёсткие требования к очередности - надо искать иной выход;
  • если очередь большая, то публикация в конец будет приводить к большой задержке обработки, а это не всегда приемлемо;
  • если сообщение всего одно - то оно мгновенно исчерпает лимиты на обработку (max retries) и сразу уйдет в DLX.

Этот алгоритм, конечно, не является идеальным и единственно верным, однако он хорошо зарекомендовал себя как алгоритм общего назначения со строгими гарантиями отсутствия потерь при возникновении нештатных ситуаций. Для тех кому он не подходит, или кому просто интересно - я накидаю побольше ссылок с комментариями. Всем спасибо!


Ссылки:

Фундаментальные ресурсы по теме распределенных систем:

  • Enterprise Integration Patterns - тут можно закопаться надолго, но это маст-хев для архитекторов и техлидов, проектирующих сложные системы;
  • Designing Data-Intensive Applications - основа основ от Мартина Клепмана. Даёт полное понимание всех нюансов и граничных кейсов в сложных системах. Рекомендую перечитывать раз в год. Находил на русском языке.