Exchange, очередь, ack и prefetch

О лекции

Продюсер RabbitMQ публикует в exchange, а не в очередь напрямую. Binding решает, в какие очереди попадёт сообщение. После ack сообщение исчезает. Prefetch ограничивает, сколько неакнутых сообщений держать на консьюмере.

RabbitMQ rabbitmq собеседование

RabbitMQ — очередь задач, не лог. Продюсер пишет в exchange, а не в очередь напрямую. Exchange сам по себе сообщения не хранит: смотрит тип и binding, кладёт копии в очереди.

Очередь — буфер потребителей. Несколько консьюмеров на одну очередь — competing consumers: брокер раздаёт сообщения. Строгий глобальный порядок между ними не обещан.

После ack сообщение пропадает. Завтра переиграть вчерашний день из той же очереди нельзя. Replay вы строите сами: архив, отдельный лог, повторная публикация из БД.

Пример. Воркер взял задачу «сжать картинку», сделал работу, отправил ack. Сообщения в очереди больше нет. В Kafka после чтения запись в логе осталась бы, пока жив retention.

Типы exchange

  • direct — routing key точно совпадает с binding key. Точечная маршрутизация.
  • topic — шаблоны вроде order.* и order.#. Удобно резать поток по домену.
  • fanout — копия во все связанные очереди, ключ игнорируется.
  • headers — маршрутизация по заголовкам, на практике реже.

Несколько очередей с одним binding получают копии. Это fan-out: каждая очередь — свой буфер и свои консьюмеры. Несколько консьюмеров на одну очередь делят сообщения, а не копируют их. Эти две схемы путают часто.

Пример. Exchange orders типа topic. Ключ order.created попадает в очередь биллинга. Ключ order.shipped — в очередь доставки. Fanout на тот же поток положил бы копию в обе очереди независимо от ключа.

Ack, nack и prefetch

Autoack — брокер считает сообщение доставленным в момент выдачи. Крэш воркера после выдачи и до работы — потеря. Это at-most-once.

Ручной ack после успешной работы — at-least-once. Крэш до ack вернёт сообщение тому же или другому консьюмеру. Дубль возможен. Nack/reject с requeue крутит сообщение снова. Без requeue его можно отправить в DLX — dead letter exchange.

Prefetch (QoS) — сколько неакнутых сообщений брокер может держать на одном канале. Prefetch=1 честнее распределяет работу и снижает throughput. Слишком большой prefetch отдаёт пачку одному толстому воркеру: он забивает память, остальные голодают, при падении пачка вернётся. Prefetch — не размер очереди, а inflight на потребителе.

ch.ExchangeDeclare("orders", "topic", durable, false, false, false, nil)
ch.QueueDeclare("orders.billing", durable, false, false, false, nil)
ch.QueueBind("orders.billing", "orders", "order.created", false, nil)
ch.Qos(20, 0, false) // prefetch: не больше 20 неакнутых
// после работы:
// d.Ack(false)         — успех, сообщение уходит
// d.Nack(false, false) — без requeue, дальше DLX если настроен

Topic exchange кладёт order.created в очередь биллинга. QoS ограничивает inflight. После работы — Ack или Nack в DLX.

Publisher confirms — отдельный слой. «Отправил в сеть» не значит «брокер принял на диск». Без confirms и без durable очереди рестарт брокера теряет то, что вы считали опубликованным. Durable очередь плюс persistent message плюс confirm — минимальный разговор про надёжность продюсера.

Типичная ошибка

Недолговечная очередь и рестарт брокера «не должно влиять». Считать ручной ack exactly-once в БД: это at-least-once, без идемпотентного consumer оплата удвоится. В отличие от Kafka, ack’нутое из той же очереди назад не прокрутишь.

На собеседовании

Картинка: producer → exchange → binding → queue → consumer. Prefetch — inflight, не размер очереди. Ack после работы — at-least-once. После ack replay нет.