Друзья, привет! Меня зовут Андрей Комягин, я CTO компании STM Labs. Мы занимаемся разработкой очень больших распределённых высоконагруженных систем для различных отраслей, включая налоговое администрирование, телеком, track & trace и многие другие. В своих проектах и архитектурах мы широко применяем open-source-решения, включая брокеры сообщений, такие как Kafka и RabbitMQ.

Совсем недавно я выступил на конференции HighLoad++ Genesis 2025: рассказал про анатомию каждого из этих брокеров, сравнил их по набору критериев и оценил результаты их нагрузочного тестирования. А теперь решил выпустить этот материал в виде статьи в блоге, чтобы читатели Хабра тоже смогли изучить нюансы и понять, на какие задачи заточен каждый из брокеров. Итак, поехали!

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

А сегодня в нашем меню Кролик

RabbitMQ — это кроссплатформенный программный брокер сообщений, написанный на языке Erlang. Он был создан компанией Rabbit Technologies Ltd в 2007 году. В 2010 году разработку приобрела компания SpringSource, а с 2013 года проект стал частью Pivotal Software.

RabbitMQ реализует протокол AMQP (Advanced Message Queuing Protocol). AMQP — это открытый протокол, предназначенный для обмена сообщениями между распределёнными приложениями и системами. Он обеспечивает надёжную передачу сообщений через сеть, поддерживая очереди, маршрутизацию и управление потоком данных.

Почему именно «Rabbit»? Потому что брокер отличается высокой скоростью работы и гибкой системой — и этими качествами напоминает шустрого кролика.

Архитектура RabbitMQ

Верхнеуровневая архитектура брокера состоит из следующих элементов:

  • Queue — сама очередь сообщений.

  • Exchange — компонент, который получает сообщения
    от отправителей и направляет их в соответствующие очереди
    в зависимости от заданных правил маршрутизации.

  • Binding — правило, которое определяет, как Exchange должен направлять сообщения в очереди на основе Routing Key.

  • Routing Key — атрибут сообщения, который используется
    для определения его маршрута.

  • Consumer — приложение, которое получает сообщения из очереди и обрабатывает их.

  • Producer — приложение, которое отправляет сообщения
    в Exchange.

С базовыми элементами архитектуры разобрались. Давайте теперь взглянем на внутрянку самого сервера:

  • Reader — отвечает за получение и обработку данных от клиентов.

  • Framing — отвечает за кодирование и декодирование сообщений AMQP.

  • Channel — это виртуальные соединения внутри физического соединения, обеспечивающие изоляцию различных операций.

  • Writer — отвечает за отправку сообщений в сеть, включая обработку подтверждений доставки сообщений.

  • Mnesia — это распределённая база данных, которая хранит конфигурацию всех элементов архитектуры: очередей, exchanges, bindings и учётных данных пользователей.

Типы Exchange

Как я уже сказал, одним из основных элементов архитектуры является Exchange. На данный момент существует достаточно большое количество его типов — под различные задачи. Рассмотрим основные:

  • Fanout — данный тип распределяет сообщения всем подключённым очередям без какой-либо фильтрации. Каждое сообщение, поступающее на обмен типа fanout, отправляется во все привязанные очереди. Это полезно для широковещательной рассылки сообщений.

  • Direct — позволяет отправлять сообщения в конкретную очередь на основе ключа маршрутизации (Routing Key).

  • Headers — использует заголовки сообщений для определения правил маршрутизации.

  • Topic — предоставляет гибкую систему маршрутизации на основе шаблонов ключей маршрутизации. Ключи маршрутизации могут содержать специальные символы-разделители (например, точки), что позволяет создавать сложные правила маршрутизации. Сообщения направляются в очереди, чьи ключи маршрутизации соответствуют шаблону, указанному при отправке сообщения.

Типы очередей:

  • Классические очереди в RabbitMQ — это базовый тип очередей. Они подходят для большинства сценариев использования и поддерживают основные функции: маршрутизацию сообщений и гарантированную их доставку.

  • Кворумные очереди обеспечивают высокую доступность и устойчивость к сбоям за счёт репликации данных между узлами кластера. Это гарантирует, что сообщения не будут потеряны в случае отказа одного или нескольких узлов.

  • Потоковые очереди оптимизированы для обработки больших объёмов данных в реальном времени. Они обеспечивают высокую пропускную способность и низкую задержку, что делает их идеальными для приложений, требующих быстрой обработки данных, таких как потоковая аналитика и обработка событий.

Кворумные очереди

Если с классическими очередями всё более-менее понятно — с ними работали практически все, — то кворумные очереди появились относительно недавно и могут вызывать вопросы. Кстати, создание данного типа очередей было вдохновлено появлением на рынке конкурента в виде Apache Kafka.

Давайте рассмотрим их устройство на основе типовой топологии кластера.

Кворумные очереди в RabbitMQ — это механизм обеспечения высокой доступности и отказоустойчивости очередей сообщений. На диаграмме мы видим, что очередь имеет один ведущий узел (leader) и две реплики (follower). Для выбора лидера используется алгоритм консенсуса Raft.

Основные свойства очередей:

  • Durable — определяет, будет ли очередь сохраняться после перезагрузки брокера RabbitMQ.

  • Auto-delete — определяет, будет ли очередь удалена, когда последний потребитель отсоединится от неё.

  • Exclusive — если очередь объявлена как эксклюзивная, значит она доступна только для одного соединения.

  • TTL — определяет время, в течение которого сообщение может находиться
    в очереди.

Протокол AMQP

А как устроен процесс отправки сообщения с точки зрения протокола AMQP? Для передачи информации используются различные типы frames. Каждый frame имеет определённый тип, channel и размер в байтах. В конце frame идет терминирующий байт 0xCE. Рассмотрим типы frames:

  • Method frame используется для того, чтобы сообщить брокеру, что клиент хочет опубликовать сообщение. Следом за ним будет отправлен header frame со свойствами этого сообщения. В данном случае мы видим, что будет использован метод basic.publish.

  • Header frame содержит свойства сообщения, включая его размер и количество body frames.

  • Сообщение может быть разбито на несколько body frames в зависимости от размера.

Header frame фактически определяет основные свойства сообщения, включая:

  • идентификатор,

  • timestamp,

  • correlation-id для связки request-response,

  • delivery-mode, который определяет параметры персистентности (persistent vs transient),

  • expiration, который определяет время жизни сообщения (т.е. предельное время нахождения в очереди) и другие атрибуты.

Свойства очередей Header Frame напрямую связаны с атрибутами AMQP: delivery-mode — это durable, expiration — это TTL.

Шардирование

Шардирование в RabbitMQ используется для распределения сообщений по нескольким очередям в контексте балансировки нагрузки. Реализовано три основных метода шардирования:

  • Consistent-hash — метод, использующий алгоритм согласованного хеширования для распределения сообщений. Для каждого binding задаётся вес, который определяет пропорцию для распределения сообщений по очередям.

  • Modulus-hash — простой метод, использующий операцию взятия остатка от деления для распределения сообщений: queue_index = hash_value % N.

  • Random — метод, при котором сообщения распределяются случайным образом. Используется простой алгоритм uniform distribution из стандартной библиотеки Erlang.

Связывание

Конечно, я не могу обойти стороной сложные топологии связывания. Для этого в RabbitMQ есть два плагина: Federation и Shovel. Они используются для обеспечения связности между различными у��лами или кластерами RabbitMQ.

Federation использует технологию репликации, создавая единую распределённую систему. Shovel же представляет собой инструмент, который может перемещать сообщения между любыми очередями и брокерами на основе заданных правил. По сути, Shovel является клиентским приложением, которое подключается к брокеру-источнику, читает сообщения и отправляет их потребителю.

На следующей диаграмме показан пример сложной топологии с балансировкой нагрузки в виде двух уровней. Для вашего проекта и модели нагрузки топология может существенно отличаться.

Что мы видим на диаграмме: верхний уровень балансировки — на базе HAProxy, нижний — на базе специализированного балансировщика AMQProxy для протокола AMQP. Далее идёт непосредственно слой брокеров, которые связаны друг с другом с помощью federation или shovel-плагина.

Батл: Kafka vs RabbitMQ

Итак, мы завершили обзор архитектуры RabbitMQ и пришло время провести сравнительный анализ. Для начала — краткий обзор тестового окружения:

Kafka — версия 3.9.1
RabbitMQ — 4.1.3

В качестве инструмента нагрузочного тестирования я воспользовался Open Messaging Benchmark Framework. Это очень мощный инструмент, который поддерживает достаточно широкий спектр систем и платформ, включая Kafka и RabbitMQ. Также можно нагрузить Pulsar, Redis, NATS и многие другие системы. При необходимости можно разработать свой драйвер для поддержки своей системы.

Пара слов о модели нагрузки:

Rate — 100000MPS
Payload — 1Kb binary (payload/payload-1Kb.data)
Model — Simple Workload 1 producer on 1 topic
Test duration — 5 min

Результаты тестирования

Первый тест — publish rate, который определяет, какую предельную нагрузку система держит на запись. Мы сразу дали высокую — 100000MPS. Кролик смог переварить только 18К MPS, а вот Kafka ожидаемо справился даже с большей нагрузкой — порядка 153К MPS.

Если говорить про throughput в MB/s, то на нашем payload в 1Kb тоже всё линейно. Пропускная способность у Кролика — 17.6 MB/s против 150 MB/s у Kafka. Примерно такие же цифры мы видим и для чтения (consumer rate) на следующем слайде. То есть consumer’ы успевают вычитывать сообщения с той же скорость, с которой идёт их запись, как в случае с RabbitMQ, так и в случае с Kafka.

Взглянем на график средней задержки при записи. Тут тоже никаких сюрпризов: задержки на запись в Kafka более чем в 2 раза ниже, чем у Кролика.

Не скажу, что результаты меня удивили. Всё вполне ожидаемо. Ведь у каждого инструмента своя область применения.

Итоги

Пришло время подвести итоги. Я сделал это в виде сводной таблицы сравнения по критериям:

Feature

RabbitMQ

Apache Kafka

Год создания

2007

2011

Разработчик

Pivotal Software

Apache Software Foundation

ЯП

Erlang

Java и Scala

Лицензия

Mozilla Public License

Apache License 2.0

Цель создания

Классический брокер сообщений

Потоковая обработка событий

Хранение

In-memory (есть простое хранение на диске)

Журнал

Retention

До подтверждения

На основе retention policy (time/size)

Маршрутизация

Сложная (различные типы exchange)

Простая (topic-based)

Принцип доставки сообщений

Push to consumers

Consumers pull

Производительность

4K-10K MPS

1 million+ MPS

Гарантии очередности

В рамках очереди

В рамках партиции

Репликация

Кворумные очереди

Встроенная репликация партиций

Протоколы

AMQP и другие

Kafka protocol

Типовое использование

Сложная маршрутизация, традиционный обмен сообщениями

Потоковая передача данных в реальном времени, конвейеры данных в высоконагруженных системах

Итак, в этой статье мы с вами:

  • Провели обзор основных компонентов и принципов работы RabbitMQ.

  • Сравнили архитектурные подходы Kafka и RabbitMQ.

  • Провели ряд сравнительных бенчмарков этих двух систем.

  • Определились с типовым сценарием использования Kafka и RabbitMQ и поняли важность выбора инструмента под конкретную задачу и проект. Ведь не существует серебряной пули!

Я надеюсь, что эта статья была для вас полезна, и что благодаря ей вы сможете определить, какой из брокеров подойдёт конкретно под ваши задачи. Подписывайтесь, ставьте лайки, оставляйте комментарии — а я постараюсь и дальше писать для вас интересные тексты!