awesome-everything EN
↑ Обратно к восхождению

Очереди, потоки, события

RabbitMQ exchanges: слой маршрутизации, который решает, кому достанется копия

Суть Producer никогда не публикует в очередь — он публикует в exchange, а bindings и routing keys решают, какие очереди получат копию. Выберешь не тот тип exchange — сообщения либо разлетятся всюду, либо тихо исчезнут.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на junior-высоте — поверхность
◷ 16 min

Платёжный сервис выходит в прод. Заказы идут, но очередь команды антифрода пуста — ноль сообщений, ни ошибок, ни исключений в логах. Producer спокойно публикует, брокер принимает. Через три дня замечают: новую очередь антифрода так и не забиндили к exchange. RabbitMQ маршрутизировал каждое сообщение в никуда и отбрасывал его беззвучно. Вызов publish всё это время возвращал успех, потому что по умолчанию exchange без подходящего binding тихо отбрасывает сообщение.

Ты публикуешь в exchange, а не в очередь

Первый сюрприз для всех, кто пришёл с моделью «очередь задач»: producer в RabbitMQ не может отправить сообщение в очередь. Он отправляет в exchange, и exchange решает — по своему типу, routing key сообщения и bindings, которые связывают его с очередями, — какие очереди (если вообще) получат копию. Binding — это правило: «exchange X, доставь в очередь Q, когда routing key совпадает с паттерном P». Одно сообщение может попасть в ноль очередей, в одну или в несколько; каждая совпавшая очередь получает свою независимую копию.

В этой непрямоте — весь смысл. Producer ничего не знает о консьюмерах, очередях и их количестве. Можно добавить нового консьюмера — ту самую очередь антифрода — создав очередь и забиндив её, без единого изменения в producer. Эта развязка и есть причина, почему RabbitMQ называют smart broker: логика маршрутизации живёт в брокере, а не в producer или консьюмере.

Четыре типа exchange — это четыре стратегии маршрутизации

У каждого exchange есть тип, и тип — это алгоритм маршрутизации. Реально используешь четыре:

ТипМаршрутизирует, когда…Применять для
directrouting key в точности равен binding keypoint-to-point маршрутизация задач (payments.refund → один пул воркеров)
fanoutвсегда — routing key игнорируетсяbroadcast: каждая забинженная очередь получает копию (инвалидация кэша, pub/sub)
topicrouting key совпадает с точечным паттерном с * / #селективная подписка (order.*.eu, logs.#)
headersатрибуты заголовков сообщения совпадают с аргументами bindingмульти-атрибутная маршрутизация, где плоского ключа мало

Direct — точное совпадение: забиндь очередь Q с ключом payments.refund, и до неё дойдут только сообщения, чей routing key — ровно payments.refund. Fanout полностью игнорирует routing key и копирует каждое сообщение в каждую забинженную очередь — самый дешёвый и быстрый путь, идеально, когда N независимых консьюмеров хотят одно и то же событие. Topic — рабочая лошадка: routing keys — это слова через точку (order.created.eu), а паттерн binding использует * для совпадения ровно с одним словом и # для нуля или более. Так order.*.eu ловит order.created.eu, но не order.created.us; logs.# ловит всё под logs. Headers игнорирует routing key и матчит по мапе атрибутов заголовков, с x-match равным all или any — мощно, но медленнее и редко того стоит; topic key обычно выражает то же намерение дешевле.

Почему это работает

Topic exchange с binding key # ведёт себя ровно как fanout (матчит всё), а topic без wildcards — как direct. Иногда «просто используют topic для всего». Работает, но fanout и direct проще для рассуждения и чуть быстрее, потому что пропускают сопоставление паттернов, — бери конкретный тип, когда намерение конкретно.

Smart broker, dumb consumer — и почему это противоположность Kafka

Это и есть сеньорная рамка, которая решает, подходит ли RabbitMQ под твою задачу вообще. RabbitMQ пушит сообщения консьюмерам и удаляет каждое, как только его подтвердили (ack). Брокер умный (он маршрутизирует, отслеживает per-message ack, переотправляет при сбое); консьюмер относительно глупый (обрабатывает то, что ему дали, и подтверждает). Подтверждённое сообщение исчезло — нет лога, чтобы перемотать назад.

Kafka — зеркальное отражение: pull-модель dumb broker, smart consumer. Kafka — это append-only лог; брокер просто хранит упорядоченные записи, а консьюмеры сами отслеживают свой offset и тянут в своём темпе. При consume ничего не удаляется — сообщение лежит весь retention window, поэтому можно его replay-нуть, добавить новую consumer group, читающую с начала, или переобработать после фикса бага. Per-message ack-and-delete в RabbitMQ даёт тонкий контроль над workflow и сложную маршрутизацию; лог Kafka даёт replay и сырой throughput.

Разрыв в throughput большой и структурный. Классические очереди RabbitMQ достигают десятков тысяч сообщений/сек на очередь только в лёгкой, транзиентной конфигурации (без durability, без publisher confirms); включи durability, персистентность или quorum-очереди — и это падает существенно, часто до нескольких тысяч. Одна партиция Kafka держит порядка 1 миллиона сообщений/сек, потому что батчит последовательные записи в лог. Обратная сторона: RabbitMQ доставляет с субмиллисекундной p50-latency (пушит по мере поступления), тогда как батчинг Kafka задаёт структурный пол примерно в 5–15 мс p50. Так что выбор не «что лучше» — это сложная per-message маршрутизация и низкая latency (RabbitMQ) против replayable высокопроизводительных стримов (Kafka).

Выбери лучший вариант

Нужно доставлять события заказов в 3 сервиса сегодня, ожидаешь добавить ещё позже, и часть заботится только о EU-заказах. Выбери топологию exchange.

Failure modes, которые поднимут тебя в 3 ночи

Три ошибки exchange-и-очередей вызывают большинство прод-инцидентов:

Unbound exchange → тихие drop’ы. Это и есть Hook. По умолчанию basic.publish не сообщает producer, была ли сообщение смаршрутизирована. Если ни один binding не совпал, брокер отбрасывает его, а publish всё равно возвращает успех. Фикс — флаг mandatory: выстави его, и несмаршрутизируемое сообщение вернётся producer через basic.return, а не исчезнет, — либо заведи catch-all очередь с #, чтобы ничего не терялось незаметно. Всегда алертуй на счётчик unroutable.

Prefetch (QoS) starvation и взрыв памяти. RabbitMQ пушит сообщения, а prefetch count (basic.qos) ограничивает, сколько неподтверждённых сообщений консьюмер может держать одновременно. Поставь слишком высоко — скажем, 1000 — и один медленный консьюмер хватает огромный backlog, пока быстрые простаивают, голодая; хуже того, эти unacked-сообщения остаются резидентно в памяти брокера, и если обработка застопорится, счётчик unacked растёт безгранично, пока узел не упрётся в memory watermark и не заблокирует всех publisher’ов. Сеньорный дефолт — низкий prefetch (начни с 1, тюнь вверх по метрикам), чтобы работа распределялась честно, а память оставалась ограниченной.

Poison message без DLX → бесконечный цикл переотправки. Консьюмер, который делает nack-with-requeue на сообщении, которое он никогда не сможет обработать (битый payload, баг, на котором он всегда падает), получает то же сообщение обратно, навсегда, пиня воркер. Фикс — dead-letter exchange (DLX): настрой очередь так, чтобы отклонённые или истёкшие сообщения dead-letter’ились, маршрутизируясь в отдельный exchange и карантинную очередь, которую можно осмотреть и переобработать. Без DLX одно poison-сообщение может застопорить весь пайплайн.

Durability и HA: classic, mirrored, quorum

Сообщение настолько в безопасности, насколько надёжна держащая его очередь. Классические очереди живут на одном узле; умрёт он — уйдут и сообщения очереди. Старым ответом были classic mirrored queues (реплики на других узлах), но их задепрекейтили и удалили в RabbitMQ 4.0 — они могли подтвердить сообщение до того, как оно безопасно реплицировано, рискуя потерей при failover. Современный ответ — quorum queues: реплицируемая очередь на Raft, которая подтверждает только когда большинство узлов имеет сообщение. Трейдофф конкретен — репликация на 3 узла держит примерно 30K сообщений/сек для сообщений в 1KB, ниже, чем нереплицируемая классическая очередь, в обмен на отсутствие потери данных, когда узел падает. Для всего, что нельзя позволить себе потерять, это та сделка, на которую идёт сеньор.

Викторина

Producer публикует в topic exchange, но очередь только что добавленного консьюмера не получает ничего, при этом нигде нет ошибок. Какова наиболее вероятная причина?

Викторина

Нужно, чтобы поздно подключившиеся консьюмеры могли replay-нуть события последней недели с начала. Какая система подходит и почему?

Расставь шаги по порядку

Расставь путь, который сообщение проходит от producer до консьюмера в RabbitMQ:

  1. 1 Producer публикует сообщение с routing key в именованный exchange
  2. 2 Exchange применяет правило своего типа к routing key против своих bindings
  3. 3 Каждый совпавший binding доставляет независимую копию в свою очередь
  4. 4 Брокер пушит сообщение консьюмеру, в пределах лимита prefetch (QoS)
  5. 5 Консьюмер подтверждает (ack); брокер удаляет эту копию из очереди
Вспомните перед уходом
  1. 01
    Коллега уверен, что RabbitMQ и Kafka — взаимозаменяемые очереди сообщений. Объясни архитектурное различие и когда что подходит.
  2. 02
    Почему высокий prefetch count вызывает и несправедливость, и аварии, и какой безопасный дефолт?
Итог

Определяющий ход RabbitMQ в том, что producers публикуют в exchange, а не в очередь, и тип exchange плюс routing key сообщения плюс bindings решают, какие очереди получат независимую копию — возможно, никакие. Direct маршрутизирует по точному совпадению ключа, fanout вещает в каждую забинженную очередь, topic матчит точечные паттерны с * и #, а headers матчит атрибуты заголовков. Это модель smart-broker, dumb-consumer с push: брокер маршрутизирует, пушит, отслеживает per-message ack и удаляет по consume — противоположность pull-логу Kafka с dumb-broker, smart-consumer, который хранит сообщения для replay. Размен — богатство маршрутизации и субмиллисекундная latency при десятках тысяч сообщений/сек на очередь (RabbitMQ) против replay и примерно миллиона сообщений/сек на партицию (Kafka). Прод-сбои предсказуемы: unbound exchange тихо отбрасывает сообщения, пока ты не выставишь флаг mandatory или catch-all binding; слишком высокий prefetch голодит быстрых консьюмеров и растит unacked-память, пока publisher’ы не заблокируются; poison-сообщение без dead-letter exchange циклится вечно. А для durability classic mirrored queues исчезли начиная с RabbitMQ 4.0 — quorum queues реплицируют через Raft и подтверждают только по большинству, стоя throughput (около 30K сообщений/сек реплицировано на три узла), чтобы не терять данные при failover. Сперва выбери правильный тип exchange и bindings; всё ниже по потоку зависит от того, что маршрутизация верна.

Продолжить восхождение ↑RabbitMQ exchanges: тест с выбором ответа
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.