Друзья, привет! Меня зовут Андрей Комягин, я 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 и поняли важность выбора инструмента под конкретную задачу и проект. Ведь не существует серебряной пули!
Я надеюсь, что эта статья была для вас полезна, и что благодаря ей вы сможете определить, какой из брокеров подойдёт конкретно под ваши задачи. Подписывайтесь, ставьте лайки, оставляйте комментарии — а я постараюсь и дальше писать для вас интересные тексты!
