RabbitMQ exchanges: чтение конфигов и маршрутизации
Суть Читай реальные конфиги binding'ов RabbitMQ и сниппеты маршрутизации, предскажи, какие queue получат копию, и выбери фикс, который senior сделает первым.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Баги маршрутизации читаются в таблице binding’ов и в вызове publish, а не в стектрейсе — его обычно нет. Читай каждый конфиг и сниппет, затем выбери поведение или фикс, который senior сделает первым.
Цель
Отработай цикл, который ты запускаешь в каждом инциденте маршрутизации: прочитай тип exchange и binding’и, предскажи, какие queue получат копию, и потянись к верному фиксу прежде, чем гадать на продюсера.
С routing key order.created.eu, опубликованным в topic exchange events, какие queue получат копию?
Heads-up Topic-binding'и не ранжируются и не исключают друг друга — нет правила «самый специфичный выигрывает». Каждый binding, чей паттерн совпал, независимо доставляет копию, поэтому все три queue получают сообщение.
Heads-up # матчит ноль или более слов, поэтому order.# полностью матчит order.created.eu. Q_audit тоже получает копию.
Heads-up Routing key может удовлетворить сколько угодно binding'ов разом; именно этот fan-out в несколько queue и есть смысл topic exchange.
Сниппет 2 — конфиг prefetch
channel.basic_qos(prefetch_count=1000) # один канал, много consumer'овchannel.basic_consume(queue="jobs", on_message_callback=handle)# handle() делает медленную CPU-bound работу, ~200ms на сообщение
Викторина
Completed
Несколько worker'ов потребляют jobs с этим конфигом, работа распределена неравномерно, а память брокера растёт. В чём дефект и фикс?
Heads-up Опрос через basic_get медленнее и всё равно не чинит распределение; push-модель в порядке. Дефект — раздутый prefetch, резервирующий большой неподтверждённый батч на одном consumer'е.
Heads-up Durability управляет тем, переживут ли сообщения рестарт брокера, а не тем, как in-flight сообщения распределяются между consumer'ами. Перекос и рост памяти идут от высокого prefetch.
Heads-up Высокий prefetch помогает, только когда consumer'ы быстры и однородны; с CPU-bound работой по 200ms он вызывает head-of-line голодание и безграничную неподтверждённую память. Низкий prefetch — senior-дефолт.
Сниппет 3 — конфиг dead-letter
declare queue "work" with arguments: x-dead-letter-exchange: "dlx" x-message-ttl: 30000 # 30sexchange "dlx" (type: fanout) -> queue "work.dead"consumer on "work": on failure -> channel.basic_nack(requeue=True)
Викторина
Completed
Poison message бесконечно зацикливается на queue work и никогда не доходит до work.dead, несмотря на конфиг dead-letter. Почему?
Heads-up Любой тип exchange может быть целью dead-letter; fanout в одну карантинную queue в порядке. Причина, почему ничего не приходит, в том, что requeue=True никогда не даёт сообщению стать dead letter.
Heads-up TTL в итоге dead-letter'нул бы нетронутое сообщение на 30s, но здесь consumer сразу переочередняет его, сбрасывая на активную queue до срабатывания TTL. Реальный дефект — флаг requeue.
Heads-up Dead-lettering работает на classic и quorum queue одинаково. Сообщение зацикливается, потому что requeue=True переотправляет его, вместо того чтобы отклонить в dlx.
Сниппет 4 — default exchange
# exchange не объявлен; публикуем с пустым именем exchangechannel.basic_publish(exchange="", routing_key="reports", body=payload)# queue с именем "reports" существует, но ТАКЖЕ привязана к topic exchange "events"
Викторина
Completed
Куда уйдёт это сообщение при пустом имени exchange и routing key reports?
Heads-up Пустое имя exchange выбирает default (безымянный) direct exchange, а не events. На этой публикации сообщение не касается events.
Heads-up Пустая строка — валидный, всегда присутствующий exchange: default direct exchange. Это единственный случай, когда routing key ведёт себя как имя queue.
Heads-up Пустое имя — это default direct exchange, а не fanout. Он доставляет только в queue, чьё имя равно routing key — здесь reports.
Итог
Маршрутизация читается в таблице binding’ов, а не в стектрейсе: topic-паттерны матчатся независимо, и каждое совпадение даёт копию (* — ровно одно слово, # — ноль или более); слишком высокий prefetch резервирует большой неподтверждённый батч на одном consumer’е и голодает остальных; dead-lettering срабатывает только на reject/expire, поэтому nack-with-requeue зацикливает poison message навсегда; а пустая строка-exchange — это default direct exchange, где routing key и есть имя queue. Сначала прочитай тип и binding’и, предскажи копии, потом чини.