Messaging & Queues

RabbitMQ

RabbitMQ — open-source брокер сообщений на Erlang с протоколом AMQP: брокер сам маршрутизирует сообщения по очередям и удаляет их после подтверждения.

Что такое RabbitMQ

RabbitMQ — open-source брокер сообщений: принимает сообщения от приложений, раскладывает их по очередям согласно правилам маршрутизации и отдаёт консьюмерам. Написан на Erlang/OTP, основной протокол — AMQP 0-9-1, через плагины поддерживает MQTT, STOMP и WebSocket.

Два свойства определяют, для чего он годится. Первое: маршрутизацию решает брокер, а не читатель — в отличие от Kafka, где консьюмер сам выбирает, что читать. Второе: сообщение исчезает из очереди после подтверждения. Это очередь задач, а не журнал событий: перечитать вчерашний день нельзя, зато можно гибко решать, кому какое сообщение достанется.

Как устроена маршрутизация

Producer ──► Exchange ──(binding: routing key)──► Queue ──► Consumer
                 │
                 └──(другой binding)───────────► Queue ──► Consumer

Продюсер никогда не пишет в очередь напрямую. Он публикует в exchange с routing key, а exchange по своим binding'ам решает, в какие очереди положить копии. Очередей может оказаться ноль (тогда сообщение просто исчезнет), одна или десять.

Тип exchange Правило Когда применяют
direct routing key точно совпал с binding key Задачи по типу: pdf, email, sms
fanout копия во все привязанные очереди Широковещательные события
topic совпадение по шаблону Подписка на классы событий
headers по заголовкам, а не по ключу Редко, когда ключа не хватает

В topic exchange routing key — слова через точку (logs.error.payments), а в шаблоне * заменяет ровно одно слово, # — ноль или больше: logs.# поймает всё из logs, *.error.* — ошибки любого сервиса.

Подтверждения и prefetch

Сообщение удаляется не в момент отдачи консьюмеру, а когда тот пришлёт ack:

def handle(ch, method, properties, body):
    try:
        process(body)
        ch.basic_ack(method.delivery_tag)                    # обработали — удалить
    except Exception:
        ch.basic_nack(method.delivery_tag, requeue=False)    # в DLQ, не по кругу

channel.basic_qos(prefetch_count=10)    # не больше 10 неподтверждённых на консьюмера
channel.basic_consume("tasks", handle)

Три ошибки, на которых спотыкаются чаще всего:

  • auto_ack=True — сообщение считается доставленным сразу при отправке. Упал воркер посреди обработки — задача потеряна безвозвратно.
  • prefetch без ограничения — один быстрый консьюмер загребает всю очередь себе в буфер, остальные простаивают, память брокера растёт. Для долгих задач ставят prefetch_count=1, для коротких — десятки.
  • requeue=True на битом сообщении — оно вернётся в очередь, снова уронит консьюмера и так по кругу. Такие сообщения отправляют в dead letter exchange.

Долговечность, DLQ и кластер

Пережить рестарт брокера сообщение может, только если выполнены оба условия сразу: очередь объявлена durable=true и сообщение опубликовано как persistent (delivery_mode=2). Одного из двух недостаточно — это классический источник «пропавших» задач. Остальное задаётся аргументами очереди:

rabbitmqadmin declare queue name=tasks durable=true \
  arguments='{"x-message-ttl":60000,
              "x-max-length":100000,
              "x-dead-letter-exchange":"dlx"}'

Сообщение живёт 60 секунд и уходит в DLX, длина очереди ограничена, а всё отвергнутое, просроченное и вытесненное попадает в dead letter exchange. DLQ стоит заводить сразу: без неё первое же битое сообщение остановит обработку.

Для отказоустойчивости используют quorum queues: они основаны на алгоритме консенсуса Raft, реплицируются на несколько узлов кластера и переживают потерю меньшинства реплик. Устаревшие классические зеркалируемые очереди в новых проектах не берут. Отдельный тип — streams: append-only лог с неразрушающим чтением, если нужна кафкаподобная семантика, не выходя из RabbitMQ.

Запуск и эксплуатация

docker run -d --name rabbit -p 5672:5672 -p 15672:15672 \
  -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=secret \
  rabbitmq:management
# 5672 — AMQP, 5671 — AMQP поверх TLS, 15672 — веб-панель management-плагина

rabbitmqctl list_queues name messages consumers   # где копится и кто читает

Наружу открывают только необходимое: панель управления на 15672 в интернете — частая находка сканеров, см. сетевые порты и TLS.

Важная особенность эксплуатации: при достижении memory watermark (по умолчанию около 40% RAM узла) RabbitMQ применяет back-pressure и блокирует продюсеров. Формально это защита, на практике выглядит как «приложение зависло на publish». Причина почти всегда одна — очередь растёт, потому что консьюмеры не справляются.

Когда RabbitMQ — правильный выбор

  • Да: раздача задач воркерам (Celery в Python, Spring AMQP в Java), сложные правила маршрутизации, приоритеты, отложенный повтор, RPC поверх очередей, разные SLA у разных типов задач.
  • Нет: сотни тысяч событий в секунду, хранение потока на недели, повторное проигрывание истории, поставка данных в аналитику. Это территория Kafka. Если же нужна просто быстрая шина между сервисами без тяжёлой эксплуатации — стоит посмотреть на NATS.

Связанные термины

Готовы запустить GPU-задачу?

Запустить GPU-сервер