Dead Letter Queue и Dead Letter Exchange (проблемное отделение почты)
Это соседи по одной цепочке: не выбор «или — или», а две части одного механизма обработки сломанных сообщений. Exchange решает, куда направить, Queue держит у себя и ждёт, пока разберутся.
На пальцах
Представь обычное почтовое отделение. Письма приходят, сортируются по адресу и уходят по маршрутам. Но иногда что-то идёт не так: адресат съехал, отказался принять, письмо слишком долго лежало без ответа. Выбрасывать такие письма почта не хочет — за них могут спросить, да и потерять жалко. Поэтому есть отдельная процедура: проблемное письмо отправляется в специальное «отделение проблемной почты». Туда ведёт маршрут через особую сортировочную станцию — это exchange. На станции письмо раскладывается по нужным ячейкам и попадает в специальный почтовый ящик-холодильник, где копится до разбора — это queue. Без такой связки любое «сломанное» письмо либо теряется, либо засоряет собой основные ящики: курьер не знает, что с ним делать, и кладёт обратно в общую стопку.
Что делает каждый
Dead Letter Exchange — это обычный exchange (чаще всего fanout или direct), на который RabbitMQ переиздаёт сообщение, когда оно «умерло»: его отвергли с requeue=false, истёк TTL (время жизни) или очередь переполнена. Exchange сам по себе ничего не хранит — только маршрутизирует по ключу: «такое сообщение — туда». Указывается в аргументах очереди через x-dead-letter-exchange.
Dead Letter Queue — обычная очередь без специального типа. Её единственная особенность — она принимает сообщения из DLX и держит их у себя, пока кто-то не разберёт: прочитает лог, поправит обработчик и вернёт сообщение в работу, или удалит совсем.
В чём разница
| Dead Letter Exchange | Dead Letter Queue | |
|---|---|---|
| Что это | Точка маршрутизации | Точка хранения |
| Тип в RabbitMQ | exchange любого типа | обычная queue |
| Хранит сообщения | нет — только пересылает | да — держит до разбора |
| Где настраивается | x-dead-letter-exchange в аргументах рабочей очереди | создаётся как обычная очередь, на неё ссылается binding от DLX |
| Что увидит пользователь | в админке — exchange со стрелкой-привязкой на очередь | в админке — очередь с накопившимися «мёртвыми» сообщениями |
| Если не сделать | сообщение «умирает» молча, без следов | DLX некуда слать, сообщения теряются или зацикливаются |
Что из этого следует
Без DLX сообщение, которое не смогли обработать, либо теряется, либо возвращается в основную очередь и засоряет её бесконечными повторами. Без DLQ — негде скопить «мёртвые» сообщения, и DLX отправляет их в пустоту. Поэтому нужны обе части: DLX задаётся как аргумент рабочей очереди, DLQ — обычная очередь с привязкой на этот exchange. Например, у интернет-магазина очередь заказов падает с ошибкой при списании оплаты — без связки DLX/DLQ ты теряешь заказ молча; с ними он лежит в отдельной очереди, ты его видишь, чинишь и возвращаешь в работу.
Как устроено
Шаги, когда сообщение «умирает» в RabbitMQ:
- Сообщение приходит в рабочую очередь (например,
orders.processing). У неё при создании указан аргументx-dead-letter-exchange = orders.dlx. Опционально —x-dead-letter-routing-key, если хочется переписать ключ маршрутизации под нужный binding. - В одном из трёх случаев сообщение считается «мёртвым»:
- потребитель сделал
basic.rejectилиbasic.nackс флагомrequeue=false; - истёк per-message TTL (свойство
expirationв сообщении) или TTL самой очереди (x-message-ttl); - очередь переполнена (
x-max-lengthпо числу сообщений илиx-max-length-bytesпо объёму).
- потребитель сделал
- RabbitMQ не удаляет сообщение и не возвращает в начало. Вместо этого он публикует его в exchange, указанный в
x-dead-letter-exchange— это и есть DLX. - DLX работает по обычным правилам маршрутизации: смотрит на routing key (исходный или переписанный через аргумент) и кладёт сообщение во все очереди, на которые у него есть binding. Один binding — сообщение попадёт в одну очередь; fanout с несколькими binding'ами — разойдётся по нескольким (например, в очередь для логов и в очередь для переотправки).
- RabbitMQ дописывает в сообщение заголовок
x-death— массив записей, где указаны исходные exchange и queue, причина смерти (rejected/expired/maxlen), время и количество попыток. Эти заголовки читает тот, кто разгребает DLQ. - Сообщение лежит в DLQ, пока его не заберёт специальный потребитель — обычно скрипт разбора, который смотрит логи, чинит данные и отправляет сообщение заново в основную очередь через её рабочий exchange.
Здесь аналогия хромает на одном: в почте «проблемное отделение» — это одно здание, а в RabbitMQ DLX и DLQ — два разных объекта, общающихся через binding. Если помнить это, цепочка «сломалось → exchange → queue → разбор» складывается правильно.