← назад к разделу

В какой-то момент приложение перестаёт общаться только через HTTP. Сервисов становится больше, и нужно передавать сообщения между ними — так, чтобы отправитель не ждал ответа и не падал, если получатель временно недоступен. Тут появляется брокер сообщений.

Самые популярные два: RabbitMQ (реализует протокол AMQP) и Apache Kafka. На первый взгляд они делают одно и то же — принимают и раздают сообщения. Но устроены фундаментально по-разному.

Проще всего увидеть эту разницу на одном и том же потоке событий.

три события: заказ создан · платёж проведён · товар отгружен RabbitMQ · очередь Kafka · журнал, 7 дней порядковых номеров нет offset 0 offset 1 offset 2 заказплатёжотгрузка ——— заказ платёж отгрузка заказплатёжотгрузка push pull consumer · сервис заказов group A · сервис заказов готов взять задачуoffset 0 · ещё не читала подтвердил ×3 — задачи снятыoffset 3 · прочитала 3 из 3 пока читает только сервис заказовсервиса аналитики ещё нетсервиса аналитики ещё нет прошло 3 дня — подключаем аналитикуаналитикаgroup B · аналитика подписалась на очередьначинает с offset 0 получила 0 из 3 событийполучила 3 из 3 событий одни и те же три события — и в очередь, и в журнал подтверждение сняло задачи из очереди; в журнале записи остались новый потребитель: очередь пуста, журнал полон Kafka отдала 3 из 3, RabbitMQ — 0 из 3

Три события ушли и в очередь, и в журнал. Сервис заказов подтвердил обработку — из очереди RabbitMQ сообщения исчезли, в журнале Kafka остались. Через три дня появляется аналитика: в Kafka она встаёт на offset 0 и получает все 3 события, из RabbitMQ — 0 из 3, потому что перечитывать уже нечего.

Обязательно

Главное различие: задача или запись в журнале

Вот ключевая разница, из которой вытекает всё остальное.

RabbitMQ относится к сообщению как к задаче. Пришло — кто-то взял — выполнил — сообщение исчезло. Как стикер с поручением на холодильнике: прочитал, выполнил, снял.

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

Эта разница определяет, какой инструмент подходит для вашей задачи.

Когда нужен RabbitMQ

Представьте интернет-магазин. Пользователь оформил заказ, и нужно отправить ему письмо. Логично поставить задачу в очередь: «отправить письмо пользователю №12345». Один из рабочих процессов возьмёт задачу, отправит письмо и «снимет стикер». Другим задача не нужна — она исполнена.

RabbitMQ создан именно для такого сценария: распределение задач между исполнителями. Брокер сам толкает (push) сообщения к потребителю и управляет очерёдностью.

Типичные случаи для RabbitMQ:

  • Отправка писем и уведомлений
  • Обработка изображений или документов в фоне
  • Вызов сервиса без ожидания ответа (асинхронный RPC)
  • Рассылка одного события десяткам сервисов (например, «обновился конфиг»)
  • Задачи с дедлайном: «если не обработали за 5 минут — выбросить»

Когда нужна Kafka

Теперь другой сценарий. Тот же интернет-магазин записывает всё, что происходит: «заказ создан», «платёж проведён», «товар отгружен». Через месяц команда аналитики хочет посмотреть все события за этот период. Ещё через месяц запускается ML-сервис — ему тоже нужна вся история.

Если использовать RabbitMQ, история уже удалена. Сообщения исчезли после обработки.

Kafka хранит все записи. Новый сервис может начать читать с самого начала и «догнать» текущий момент. Именно поэтому Kafka называют журналом событий (event log).

Потребители Kafka сами тянут (pull) сообщения в своём темпе. Если один сервис отстал — журнал его дождётся.

Типичные случаи для Kafka:

  • Журнал событий с возможностью перечитать историю
  • Высокая нагрузка (сотни тысяч событий в секунду)
  • Несколько независимых команд читают один поток данных
  • Стриминговая аналитика в реальном времени
  • Сбор событий из баз данных (CDC через Debezium)
  • Event Sourcing — когда события первичны, а состояние вторично

Семь критериев сравнения

1. Природа сообщения

Начните с вопроса: что вы отправляете?

Команда — указание что-то сделать. «Отправить письмо», «обработать файл». После выполнения она больше не нужна. → RabbitMQ.

Событие — факт, что что-то произошло. «Заказ создан», «пользователь зарегистрировался». Может понадобиться многим сервисам, сейчас и в будущем. → Kafka.

RabbitMQ 3 события заказа одна очередь два потребителя порядок смешан Kafka 3 события заказа ключ orderId партиция P1 порядок сохранён

Одни и те же три события одного заказа: в очереди RabbitMQ их разбирают два потребителя и порядок ломается, в Kafka ключ orderId сводит их в одну партицию.

2. Порядок сообщений

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

RabbitMQKafka
Гарантия порядкаВнутри одной очередиВнутри одной партиции
Параллельная обработка с сохранением порядкаЕсть, но надстройками поверх моделиЕстественно через ключ партиции

В Kafka можно указать ключ (например, userId) — и все события этого пользователя будут идти в одну партицию, строго по порядку. При этом разные пользователи обрабатываются параллельно. В RabbitMQ то же самое собирается, но из отдельных деталей: обменник consistent-hash разводит сообщения по очередям по хешу ключа, режим single active consumer держит на очереди одного читателя за раз, у потоков есть super-streams с разбиением на части. Всё это работает — просто это не встроенная модель, а надстройки, которые надо знать и включать.

3. Пропускная способность

Грубые ориентиры на одном узле:

Пропускная способность
RabbitMQ (надёжный режим)30–50 тыс. сообщений/сек
Kafka (надёжный режим)сотни тысяч, на пике — около миллиона сообщений/сек

Эти цифры — порядок величины, а не обещание. Они сильно зависят от условий: размера сообщения, пакетирования на стороне отправителя, числа партиций и того, скольких реплик ждёт подтверждение. Верхняя граница Kafka — это мелкие сообщения, крупные пачки и много партиций; на сообщениях покрупнее и с подтверждением от всех реплик она падает в разы.

Kafka строилась под огромные объёмы: последовательная запись на диск, пакетная обработка. Для IoT-телеметрии, потоков кликов, сбора логов — Kafka единственный реалистичный вариант. RabbitMQ комфортно работает на тысячах сообщений в секунду.

4. Хранение истории

RabbitMQ: из обычной очереди сообщение исчезает после того, как его взяли и подтвердили, — перечитать нельзя. Исключение — потоки (streams), появившиеся в версии 3.9: они устроены как журнал и позволяют читать одно и то же многократно, но это отдельный тип, а не поведение очередей.

Kafka: сообщения хранятся настраиваемое время (по умолчанию 7 дней, можно дольше). Новый сервис, появившийся через неделю, прочитает всё с самого начала.

ЗадачаЛучше
Перезапустить обработку на старых данныхKafka
Новый сервис должен получить историюKafka
Аудит и хранение всех событийKafka
Доставить и забытьRabbitMQ

5. Гибкость маршрутизации

RabbitMQ управляет маршрутизацией на уровне брокера. Отправитель публикует в exchange, брокер по правилам раскладывает по очередям. Можно динамически менять кто что получает — отправитель об этом не знает. Отдел аналитики захотел получать все события оплат: администратор заводит новую очередь и привязку к тому же exchange по ключу payment.*, а код сервиса оплат не меняется и даже не перезапускается.

Kafka переносит маршрутизацию из брокера в код. Отправитель выбирает топик и ключ — ключ и партиционер решают, в какую партицию ляжет запись; читатель подписывается на топик или сразу на набор топиков по шаблону. Добавить нового читателя просто: новая группа потребителей, отправителя не трогаем. А вот поменять сами правила — «эти события теперь в отдельный поток» — значит завести новые топики и выкатить код обеих сторон, тогда как в RabbitMQ это правка привязок на брокере.

6. Модель доставки

RabbitMQ (push): брокер сам доставляет сообщения к потребителю. Если потребитель не справляется — брокер включает обратное давление.

Kafka (pull): потребитель сам запрашивает следующую порцию в своём темпе. Если отстал — журнал его дождётся. Брокер не перегружается от медленных потребителей.

Практически: Kafka лучше переносит неравномерную нагрузку. Пик записи в журнал не мешает медленным читателям.

7. Операционная сложность

RabbitMQKafka
Минимальный кластер для производства3 узла3 узла (роли брокера и контроллера можно совместить)
МониторингВстроенный UI + PrometheusJMX + специальные инструменты
Порог вхождения для командыСреднийВысокий
Кросс-датацентровая репликацияОтносительно простоТребует понимания MirrorMaker 2

Строку про репликацию между дата-центрами стоит читать с оговоркой. Federation и Shovel у RabbitMQ действительно проще MirrorMaker 2, но переносят они только сами сообщения: позиции потребителей на другой стороне не появляются, порядок при переносе не гарантирован. Для «переключились на резервный кластер и поехали дальше» этого хватает, для «продолжить ровно с того места, где остановились» — нет.

Kafka — серьёзная операционная инвестиция. Один раз настроить правильно — и она работает годами. Но если команда небольшая и нет опытного администратора, RabbitMQ окажется предсказуемее.

Что умеет только RabbitMQ

Семь критериев выше сравнивают общую модель, а выбор часто решают четыре возможности, которых у Kafka нет вовсе. Если нужна хотя бы одна, спор о «журнале против очереди» можно не начинать.

Приоритеты. У очереди задают x-max-priority, и сообщение с высоким приоритетом обходит накопившиеся низкие. В Kafka приоритетов нет и быть не может: партиция — это упорядоченный журнал, читаемый строго подряд. Обходятся отдельным топиком под «срочное» и отдельными потребителями, то есть приоритетов нет, а есть две трубы, между которыми вы сами делите внимание.

Отложенная доставка. «Повторить через тридцать секунд», «напомнить через час» в RabbitMQ делается очередью с TTL и обменником недоставленных или плагином отложенной доставки. В Kafka сообщение доступно сразу и лежит до истечения срока хранения; отложить его нельзя — потребителю пришлось бы остановить чтение всей партиции, а это остановит и всё остальное.

Срок жизни у отдельного сообщения. В RabbitMQ у каждого сообщения бывает свой TTL: «этот запрос бессмысленно обрабатывать позже, чем через минуту». В Kafka срок хранения задаётся топиком целиком и означает не «устарело», а «удалим с диска».

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

Из того же корня растёт и пятое отличие, менее заметное: в RabbitMQ работа считается взятой в руки. Сообщение выдано потребителю, не подтверждено и вернётся другому, если тот упал. В Kafka упавший потребитель оставляет смещение на месте, и после ребаланса весь блок будет перечитан заново — это разные модели ответственности, и в задачах-командах первая обычно удобнее.

Что умеет только Kafka

Обратная сторона такая же конкретная.

Транзакции продьюсера и чтение только зафиксированного. Kafka умеет то, чего в RabbitMQ нет совсем: несколько отправок в разные топики плюс сдвиг смещения потребителя фиксируются как одна транзакция. Отправитель открывает транзакцию, пишет, фиксирует; потребитель с isolation.level=read_committed не видит ничего из незафиксированной транзакции. Это и есть основа обработки «прочитал, посчитал, записал» без дублей внутри Kafka — то, что в её документации называется exactly-once. Оговорка важная: гарантия действует внутри Kafka, а запись во внешнюю базу в эту транзакцию не входит, поэтому идемпотентность получателя никуда не девается.

Перечитать историю. Смещение потребителя — это просто число, и его можно сдвинуть назад: новый сервис прочитает события за месяц и построит своё состояние, а после ошибки в обработчике можно перечитать вчерашний день. В RabbitMQ прочитанное и подтверждённое сообщение исчезает; чтобы перечитать, нужна своя копия в базе. (У RabbitMQ есть потоки — streams, — где чтение с начала возможно, и это осознанный шаг в сторону модели Kafka.)

Сжатие по ключу. Топик с cleanup.policy=compact хранит последнее значение для каждого ключа и выбрасывает предыдущие. Получается журнал, который одновременно является снимком текущего состояния: новый потребитель, прочитав его целиком, получает актуальную картину. Это то, на чём держатся таблицы в потоковой обработке и раздача справочников по сервисам; в RabbitMQ аналога нет.

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

Много независимых читателей одного потока. Добавить ещё одну группу потребителей в Kafka ничего не стоит: данные не копируются, каждая группа держит своё смещение. В RabbitMQ каждый новый читатель — это новая очередь, куда обменник кладёт копию сообщения, то есть место на диске и отдельная очередь, за которой надо следить.

Что это стоит в эксплуатации

«Операционная сложность» заявлена одним из критериев, и её стоит перевести в конкретику: что придётся поднять, кому дежурить и что тяжелее чинить ночью.

Из чего состоит установка. Минимальная отказоустойчивая RabbitMQ — три узла в кластере с надёжными очередями; узлам нужен быстрый диск и немного памяти, а сам брокер держит в памяти индекс очередей и часть сообщений. Минимальная Kafka — три брокера (в современных версиях без ZooKeeper, роль контроллера берут они же), и главный ресурс здесь диск: он обязан вмещать весь срок хранения, умноженный на число реплик. Ориентир для оценки: сто гигабайт событий в сутки при недельном сроке хранения и трёх репликах требуют около двух терабайт на кластер, и это ещё до запаса на всплески.

Кто дежурит. RabbitMQ обслуживает тот же, кто обслуживает приложение: настроек мало, метрик десяток, разбор почти всегда сводится к «очередь растёт — потребитель не успевает». Kafka требует человека, который понимает партиции, смещения, ребалансы и сроки хранения; это не обязательно отдельная роль, но обязательно кто-то, кто прочитал про неё больше, чем руководство по запуску. Разница особенно заметна в команде из четырёх разработчиков без выделенной эксплуатации.

Что ломается и чем это лечится. У RabbitMQ типичные аварии — очередь, которая выросла и съела память узла (лечится пределами длины и обратным давлением), и расхождение свойств очереди при выкате (лечится удалением и пересозданием очереди, то есть окном простоя потребителей). У Kafka типичные аварии — отставание потребителя, которое не успеть закрыть до истечения срока хранения (данные уедут с диска), бесконечный ребаланс группы из-за долгой обработки, и кончившееся место на диске, после которого брокер останавливается. Ночью первые чинятся быстрее: «перезапустить потребителя» против «понять, почему группа не может договориться».

Чего стоит переезд. Смена брокера — это не замена библиотеки: меняются гарантии, порядок, способ повтора и модель ответственности за прочитанное. Поэтому цена ошибки выбора считается не в деньгах за узлы, а в переписывании обработчиков, и именно поэтому стоит выбирать по модели данных, а не по популярности.

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

Когда использовать обе вместе

Это не «возьмём обе на всякий случай», а конкретная архитектура.

Сервис получает команды через RabbitMQ (обработать изображение, отправить письмо). После выполнения он публикует факт в Kafka (изображение обработано, письмо отправлено). Аналитика, мониторинг, ML-сервисы читают Kafka и не знают про очередь команд.

команда HTTP-запрос RabbitMQ сервис событие сервис Kafka аналитика, мониторинг, ML

Команда адресная: её отправляют одному исполнителю и ждут, что он её выполнит. Событие — факт: его читают все, кому интересно, и отправитель о них не знает.

Команды атомарны, исчезают после обработки. События накапливаются для истории.

Две оговорки к этой схеме, о которых обычно узнают потом.

Мост между брокерами, если он всё-таки нужен. Иногда данные надо не породить в обоих, а перелить из одного в другой: события из Kafka отдать внешнему подрядчику, который умеет только AMQP, или собрать в Kafka поток из старой системы на RabbitMQ. Готовых способов два. Kafka Connect с коннектором для RabbitMQ (источник или приёмник) — отдельный процесс, который читает одно и пишет в другое, с собственным управлением смещениями. На стороне RabbitMQ похожую роль играют встроенные механизмы Shovel и Federation, но они переливают между брокерами AMQP, а не в Kafka. Своя перекладывающая служба на десять строк выглядит проще, и это ловушка: она должна уметь повторы, идемпотентность, наблюдаемость и обратное давление, то есть всё то, что в готовых коннекторах уже написано.

Две модели — две дисциплины. Держать оба брокера означает: два набора метрик и панелей, два способа разбирать сбои, два способа повторять и две разные привычки у разработчиков. В маленькой команде это чаще всего дороже, чем польза от точного соответствия инструмента задаче. Поэтому вводить второй брокер стоит, когда первый уже не справляется с конкретной задачей, а не заранее «для чистоты архитектуры»; и в этот момент полезно записать в правилах проекта, что едет через какой брокер, — иначе через год через оба поедет одно и то же.

Типичные ошибки выбора

«Возьмём RabbitMQ, потом перейдём на Kafka» — если данные нужны как журнал событий, начинайте с Kafka сразу. Переход — это переписывание логики, не замена клиента.

«Kafka, потому что крупные компании её используют» — крупные компании обрабатывают десятки миллионов событий в секунду. На нескольких тысячах событий в день RabbitMQ проще и дешевле в обслуживании.

«Сделаем очередь задач поверх Kafka» — технически возможно, но Kafka не оптимизирована под короткоживущие задачи. Распределение рабочих задач — это RabbitMQ.

«Будем хранить запросы пользователей в Kafka как в базе данных» — Kafka не база данных. Если нужен произвольный поиск по содержимому — это хранилище событий плюс PostgreSQL или аналог, не сырая Kafka.

Дополнительно: при первом чтении можно пропустить

Глубже: порядок в RabbitMQ: single active consumer, consistent-hash и super streamsрасширенное

«Порядок в RabbitMQ из коробки нет» верно для очереди с несколькими потребителями: они разбирают её вперемешку, и события одного заказа обрабатываются параллельно. На практике порядок закрывают тремя штатными средствами.

Single active consumer. Аргумент очереди x-single-active-consumer: true разрешает работать одному потребителю, остальные подключены и ждут; упал активный, следующий подхватывает с того же места. Порядок сохраняется, отказоустойчивость есть, параллелизма нет: одна очередь, один поток. Годится для очередей с небольшим потоком, где порядок важнее скорости.

Consistent-hash exchange. Плагин rabbitmq_consistent_hash_exchange даёт exchange, который выбирает очередь по хешу ключа маршрутизации: все сообщения с order-42 попадают в одну из N очередей, и если у каждой из N очередей single active consumer, получается ровно модель Kafka: N партиций, порядок внутри ключа, параллелизм между ключами. Число очередей выбирают заранее, как число партиций, и меняют с переразбиением; вес очереди задаётся ключом привязки.

Super streams. Для потоков RabbitMQ (тип очереди stream, журнал с оффсетами) есть партиционирование из коробки: super stream это набор потоков-партиций с маршрутизацией по ключу, потребители читают каждый свою партицию с single active consumer на партицию, и переподключение отдаёт партицию другому. Это и есть ответ на «нужны партиции Kafka, но у нас RabbitMQ»: тот же порядок, тот же повтор чтения по оффсету, свой протокол и клиент (rabbitmq-stream-client), без обмена по AMQP.

Правило выбора: порядок нужен редко и потоки малы, single active consumer; нужны и порядок, и параллелизм по ключу над обычными очередями, consistent-hash с очередью на партицию; нужен журнал с перечитыванием, super streams или уже Kafka, о чём таблица выше.

Глубже: брокер не нужен: очередь заданий в PostgreSQLрасширенное

Развилка «RabbitMQ или Kafka» пропускает третий ответ, который для многих сервисов правильный: ни то, ни другое. Если задачи фоновые, поток десятки в секунду, а потребитель тот же сервис, очередь делают таблицей в той базе, которая уже есть.

Таблица jobs(id, type, payload, run_at, attempts, status), обработчик в цикле берёт пачку: SELECT ... WHERE status = 'NEW' AND run_at <= now() ORDER BY run_at FOR UPDATE SKIP LOCKED LIMIT 10, обрабатывает и помечает в той же транзакции. SKIP LOCKED пропускает строки, которые держит соседний экземпляр, поэтому потребителей может быть много без дублей; run_at даёт отложенную доставку бесплатно; attempts с растущим run_at это повторы с паузой; завершённые строки чистят по возрасту или переносят в архивную таблицу. Механика блокировок разобрана в статье про режимы блокировок и в разделе про блокировки PostgreSQL.

Что получаете сверх брокера: задача ставится в той же транзакции, что и бизнес-изменение, и проблема outbox исчезает, потому что очередь и есть таблица; наблюдаемость обычным SELECT; ни одной новой системы в эксплуатации. Что теряете: при потоке в тысячи задач в секунду таблица становится горячей, с раздуванием от обновлений и конкуренцией за индекс; опрос с паузой даёт задержку до секунды, если не будить LISTEN/NOTIFY; потребители из других сервисов должны ходить в чужую базу, чего делать нельзя, и тогда очередь превращается в интеграционную точку, для которой брокер и нужен.

Граница проходит примерно так: задачи одного сервиса, до сотен в секунду, с потребностью в отложенном запуске и транзакционности, это таблица; события для других сервисов, поток в тысячи, перечитывание истории, это брокер. Первое часто заводят вначале, и переезд на брокер потом это выкат отправителя, если обработчики с самого начала идемпотентны.

Коротко

  • RabbitMQ = очередь задач. Сообщение исчезает после обработки. Брокер толкает к потребителю. Kafka = журнал событий. Сообщения хранятся и могут быть прочитаны заново. Потребитель тянет в своём темпе.
  • Выбирайте RabbitMQ для распределения задач между исполнителями, асинхронного RPC, гибкой маршрутизации. Выбирайте Kafka для хранения истории событий, высокой нагрузки, стримингового анализа.
  • Порядок событий по ключу в Kafka встроен; в RabbitMQ требует дополнительных усилий. Порядок в RabbitMQ закрывают single active consumer (один поток), consistent-hash exchange с очередью на партицию (порядок по ключу и параллелизм) и super streams (партиционированный журнал).
  • Kafka операционно сложнее — оправдана, когда действительно нужны её возможности. Эксплуатация различается больше, чем протоколы: Kafka требует диска на весь срок хранения и человека, который понимает партиции и ребалансы, а ночью «перезапустить потребителя» дешевле, чем разбираться, почему группа не договорилась.
  • Обе вместе — не «на всякий случай», а конкретный паттерн: RabbitMQ для команд, Kafka для событий.
  • Третий ответ на развилку: очередь заданий таблицей с FOR UPDATE SKIP LOCKED для фоновых задач одного сервиса до сотен в секунду; брокер нужен для событий между сервисами и перечитывания истории.
  • Только у RabbitMQ: приоритеты, отложенная доставка, TTL у отдельного сообщения и маршрутизация отвергнутого; в Kafka очередь недоставленных делают руками отдельным топиком.
  • Только у Kafka: транзакции продьюсера с read_committed, перечитывание истории по смещению, сжатие по ключу и порядок по ключу вместе с параллелизмом.
  • Переливать между брокерами лучше готовым коннектором, а не своей службой на десять строк, и помнить, что два брокера — это два набора метрик и две дисциплины разбора сбоев.

Что почитать дальше

  • Протокол AMQP — как устроена модель доставки RabbitMQ.
  • Паттерны обмена сообщениями: Java, Go, Node, Python — work queue, pub/sub, RPC, идемпотентность.
  • Распределённые паттерны — Saga, Outbox, Idempotent Consumer (применимы к обоим брокерам): Java, Go, Node, Python.