Организация и использование Dead Letter в RabbitMQ

версия для печати
Dead Letter в RMQ

Есть как минимум два повода, почему вы нашли эту статью:

  1. Вы хотите использовать фичу Dead Letter в RMQ, но лень читать английские мануалы
  2. Вы уже прочитали RMQ: Dead Letter Exchanges, RMQ: Policies и возможно эту статью. И теперь у вас такая же каша в понимании предмета, как было у меня.

Моя статья не претендует на полноту освещения вопроса. Она для того, чтобы понять, как заставить эту фичу работать хотя бы как-то, чтобы дальше можно было экспериментировать. Я на это потратил полтора дня, возможно сэкономлю вам время.

Прим: вы можете сразу перейти в конец статьи и прочитать резюме. Возможно там вы найдете ответы и не придется читать ее полностью.

Механизм Dead Lettering задуман для конкретных ситуаций в обмене сообщениями:

  • Сообщение отрицательно воспринимается потребителем | A message is negatively acknowledged by the consumer
  • Время жизни сообщения истекло | The TTL of a message expires
  • Забилась очередь | The queue reaches capacity

Мое первое заблуждение: механизм точно не подходит для ситуаций, когда consumer взял сообщение и отвалился. Например, возникло исключение и приложение упало. В этих ситуациях сообщение либо будет возвращено в свою очередь, если у нее настроен обязательный ack ответ потребителя, либо потеряно. Так же Dead Letter не работает с отправкой сообщений в несуществующую очередь. Хотя в документации где-то была заявлена такая поддержка, но у меня не получилось. Сообщения просто исчезали.

Еще раз: чтобы сообщение попало в Dead Letter, обработчик должен вернуть nack ответ вместо ack. Это было вообще неочевидно, я даже не знал, что есть такой ответ. Ситуации с TTL сообщения и переполнением очереди хотя бы как-то понятны.

Согласно документации, чтобы сообщения из очереди попадали в Dead Letter, ей нужно навесить параметр x-dead-letter-exchange в значении которого указывается выделенный под сообщения обменник. Есть еще необязательный x-dead-letter-routing-key. Этот ключ будет назначен сообщению при его переносе из своей очереди в обменник Dead Letter. Если такой ключ не задан, сообщение переносится с исходным ключом.

И вот мое второе заблуждение после изучения документации: необязательность всех настроек, кроме какого-то особого обменника. Это не так! Выделяемый под Dead Letter обменник - это такой же обменник, как и любой другой. И обязательно нужно создавать/назначать очереди, которые будут забирать из него сообщения. Т.е. сама фича Dead Letter - это обычный обменник/очередь и routing keys для их связи. И эта кухня работает по общим правилам RMQ. Про x-dead-letter-routing-key поговорим отдельно, там есть ньюансы.

Можно двумя способами настроить очередь на пересылку ее проблемных сообщений в Dead Letter:

  • Через политики RMQ
  • Через параметры создания очереди

Через политики заметно удобнее, расскажу ниже почему. Пока в картинках, как это делается.

По непонятным мне причинам примеры в официальной документации используют консольную программу rabbitmqctl. У нас же есть админка RabbitMQ и с ней на проде куда проще, чем через консоль рулить сервером.

Политики в админке (сразу с описанной для Dead Lettering):

Политики

Прим: форма создания политики так же используется для ее редактирования, хотя и неочевидным способом. В нее нужно скопировать имя политики и поменять остальное, что требуется. Тогда политика перезапишется.

Описанная политика в моем примере:

  • Name: dead_letter.mailer - название политики. Любое понятное в вашей бизнес-логике.
  • pattern: mailer.* - фильтр имен очередей/обменников, к которым нужно применить политику. Вроде бы regex, но это не точно.
  • Apply to: queues - к чему примерять политику: очередям, обменникам или и тем и другим.
  • Definition - самая мутная часть. Это и есть описание политики, т.е. что именно применить к очередям/обменникам, выбранным заданным фильтром.
  • Priority: 0 - приоритет данной политики при совпадении с другими. Тоже мутная тема. Вроде как 0 - наивысший приоритет, но непонятно в какую сторону другие приоритеты описываются.

Политику с Dead Letter можно назначить только очередям, вешать на обменники бесполезно. Пересылка не будет работать, хотя и ошибок не увидите.

Давайте подробнее про definition. Политики могут быть разного назначения, и определения в них будут зависеть от цели. Конкретно для Dead Letter мы хотим, чтобы отфильтрованным очередям назначались:

  • dead-letter-exchange - указывает обменник, куда скинуть проблемное сообщение
  • dead-letter-routing-key - каким ключом связать перекинутые сообщения со спец.очередью в этом обменнике

Обратите внимание, тут нет префикса x- названиях параметров.

Так выглядят очереди с назначенными политиками:

Политики на очередях
Очередь с политикой, в деталях

Теперь создаем обменник, спец.очередь, и задаем связи:

Обменник

Параметры обменника/очереди могут быть любыми, на ваше усмотрение. Помним, что это обычные сущности в RMQ.

Имя очереди _bad_messages специально так задал, чтобы понимать, где что. Ключ dlx_key - просто по аналогии с примером из найденной статьи, он не имеет тайного смысла.

На заметку: начиная с v3.10 в RabbitMQ есть поддержка dead lettering в кворумных очередях с гарантией доставки, см. Queues - Dead Lettering. Как я понял, это политика с особыми настройками, которая не позволит потеряться важному сообщению при его переносе в DLX (dead letter exchange). Но такое решение требует больше ресурсов. При том политику нужно правильно описать, с дополнительными параметрами. Я в это не лез, у нас старый RMQ на проде.

Теперь логика такая: любые отбракованные сообщения их всех очередей mailer.* будут переложены в очередь _bad_messages. Именно благодаря описанному routing key в политиках и в спец.очереди она становится сборной, не требуя дополнительной настройки. Т.е. любые новые очереди, названные с префиксом mailer.*, автоматически будут подключены с пересылкой сообщений в обменик dead_letter и появятся в очереди _bad_messages.

Перекинутое сообщение выглядит так в очереди _bad_messages:

Перекинутое сообщение

RMQ в причину (reason) пишет значение из списка: rejected, expired, maxlen, delivery_limit. Он не может знать, что там случилось у consumer и кастомизация причин тут не поддерживается.

На payload не обращайте внимания, че попало передавал. Сообщение тут оказалось только потому, что в обработчике я прописал $message->nack(), т.е. явно отказался его обрабатывать. Это один из описанных в документации случаев, когда правильно настроенная фича Dead Letter реально работает.

Про ключ dead-letter-routing-key: dlx_key и сборную очередь

Можно/если не использовать это определение в политике, тогда чтобы создать сборную очередь, придется связывать ее по ключам из всех очередей, чьи сообщения нужно собирать. На примере очередей Mailer, выглядело бы так:

Routing keys

Примечательно то, что по умолчанию есть default exchange binding, т.е. у очереди сейчас 3 связи с обменником. Но если явные связи удалить, то сообщения в очередь не пойдут.

Короче, проще сделать, как выше описано: с явным указанием выделенного ключа и его определением в политике.

Dead Lettering через параметры создания очереди

Прикручивать Dead Lettering через параметры очереди крайне неудобно, потому эти параметры относятся к описанию очереди для ее создания. И если похожая очередь уже есть, но без параметров, consumer не запустится из-за конфликта описаний. Нужно будет удалить старую очередь.

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

И кстати, фича Dead Letter в документации находится в разделе расширений RabbitMQ. Т.е. она отсутствует в протоколе AMQP и возможно не переносится на другие реализации брокеров. Так что чем меньше мы в коде по ней напишем, тем лучше для нас же в теоретическом будущем. Тут опять выгоднее использовать политики, которые только на сервере RMQ хранятся.

Резюме
  • Фича Dead Letter - это расширение в RabbitMQ, оно не описано в протоколе AMQP.
  • Обменник для dead lettering может быть выделен специально или использован любой подходящий по вашей логике.
  • Очередь для отвергнутых сообщений так же может быть либо созданная специально либо любая, походящая по логике.
  • Чтобы задать пересылку сообщений, в исходной очереди нужно описать, как минимум, параметр x-dead-letter-exchange.
  • (Массовую) конфигурацию исходных очередей можно провести через политики RMQ.
  • Чтобы сообщение было перенесено в Dead Letter, либо обработчик должен явно от него отказаться, отправив nack, либо TTL сообщения истечет до его обработки, либо очередь забьется и не примет новое сообщение.
[1oo%, EoF]

Понравилась статья? Расскажите о ней друзьям:


Комментарии
Для работы модуля комментариев включите javaScript


Показать/скрыть правила
Имя
[i] [b] [u] [s] [url]
:-) ;-) :D *lol* 8-) :-* :-| :-( *cry* :o :-? *unsure* *oops* :-x *shocked* *zzz* :P *evil*

Осталось 1000 символов.
Код защиты от спама Обновить код
Каждый комментарий проходит ручную модерацию. 100% фильтрация спама.
Продвижение
Время
Метки
Щелкни мышей, чтобы закрыть