В какой-то момент приложение перестаёт общаться только через HTTP. Сервисов становится больше, и нужно передавать сообщения между ними — так, чтобы отправитель не ждал ответа и не падал, если получатель временно недоступен. Тут появляется брокер сообщений.
Самые популярные два: RabbitMQ (реализует протокол AMQP) и Apache Kafka. На первый взгляд они делают одно и то же — принимают и раздают сообщения. Но устроены фундаментально по-разному.
Проще всего увидеть эту разницу на одном и том же потоке событий.
Три события ушли и в очередь, и в журнал. Сервис заказов подтвердил обработку — из очереди 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 их разбирают два потребителя и порядок ломается, в Kafka ключ orderId сводит их в одну партицию.
2. Порядок сообщений
Порядок в RabbitMQ рушится не в очереди, а на выходе из неё. Очередь отдаёт сообщения по порядку, но два потребителя одной очереди получают их по очереди и обрабатывают параллельно — второе может завершиться раньше первого. А если первое отклонили и оно вернулось в очередь, оно встанет уже за теми, что были отданы после него.
| RabbitMQ | Kafka | |
|---|---|---|
| Гарантия порядка | Внутри одной очереди | Внутри одной партиции |
| Параллельная обработка с сохранением порядка | Есть, но надстройками поверх модели | Естественно через ключ партиции |
В 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. Операционная сложность
| RabbitMQ | Kafka | |
|---|---|---|
| Минимальный кластер для производства | 3 узла | 3 узла (роли брокера и контроллера можно совместить) |
| Мониторинг | Встроенный UI + Prometheus | JMX + специальные инструменты |
| Порог вхождения для команды | Средний | Высокий |
| Кросс-датацентровая репликация | Относительно просто | Требует понимания 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 и не знают про очередь команд.
Команда адресная: её отправляют одному исполнителю и ждут, что он её выполнит. Событие — факт: его читают все, кому интересно, и отправитель о них не знает.
Команды атомарны, исчезают после обработки. События накапливаются для истории.
Две оговорки к этой схеме, о которых обычно узнают потом.
Мост между брокерами, если он всё-таки нужен. Иногда данные надо не породить в обоих, а перелить из одного в другой: события из 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, перечитывание истории по смещению, сжатие по ключу и порядок по ключу вместе с параллелизмом. - Переливать между брокерами лучше готовым коннектором, а не своей службой на десять строк, и помнить, что два брокера — это два набора метрик и две дисциплины разбора сбоев.