RabbitMQ vs Kafka: очереди против лога
RabbitMQ vs Kafka: очереди против лога
Оба называются «брокерами сообщений», но устроены принципиально по-разному. RabbitMQ - это умная очередь с push-доставкой. Kafka - это распределённый лог с pull-доставкой. Неправильный выбор стоит дорого, потому что мигрировать потом тяжело.
Этот трек строится по принципу «сначала практика»: event-driven уже объяснил зачем нужны брокеры. Здесь - как с ними работать.
Модель данных: очередь vs лог
RabbitMQ хранит сообщения в очередях. Когда consumer забирает сообщение и подтверждает (basic.ack), оно удаляется. Каждое сообщение доставляется ровно одному consumer-у из группы (round-robin или при fair dispatch). Push-модель: брокер сам отправляет сообщения consumer-ам.
Producer → Exchange → Queue → Consumer (ack → delete)
↘ Queue2 → Consumer2
Kafka хранит сообщения в партиционированном логе (topic). Сообщения не удаляются после чтения - они хранятся по retention policy (дни, объём). Consumer читает по offset-у в своём темпе. Pull-модель: consumer сам запрашивает следующую порцию.
Producer → Topic/Partition[0] → Consumer Group A (offset 42)
→ Consumer Group B (offset 17)
→ Topic/Partition[1] → ...
Ключевое отличие: Kafka позволяет нескольким независимым consumer group-ам читать одни и те же события с разных позиций. RabbitMQ - нет.
Exchange и routing в RabbitMQ
RabbitMQ не отправляет сообщения напрямую в очередь. Producer публикует в exchange, exchange решает в какую очередь(и) отправить по routing key.
Типы exchange:
- direct - точное совпадение routing key (
order.created→ очередьorders) - fanout - во все привязанные очереди (broadcast)
- topic - паттерн с wildcards (
order.*→order.createdиorder.updated) - headers - по заголовкам сообщения (редко)
// Объявляем exchange
ch.ExchangeDeclare("events", "topic", true, false, false, false, nil)
// Привязываем очередь с routing key
ch.QueueBind("analytics", "lesson.*", "events", false, nil)
ch.QueueBind("email", "lesson.completed", "events", false, nil)
Это позволяет строить гибкие топологии без изменения producer-а.
Partitions и Consumer Groups в Kafka
Топик в Kafka разбит на партиции. Партиции - единица параллелизма: один consumer в группе читает одну или несколько партиций.
Topic: lesson-events (3 партиции)
Partition 0: msg1, msg3, msg5, ...
Partition 1: msg2, msg4, msg6, ...
Partition 2: msg7, msg8, ...
Consumer Group "analytics" (3 воркера):
worker-1 → Partition 0
worker-2 → Partition 1
worker-3 → Partition 2
Важно: порядок сообщений гарантирован внутри партиции, но не между ними. Если нужен порядок событий пользователя - кладём в одну партицию по user_id как partition key.
Количество consumer-ов в группе не может превышать количество партиций: лишние воркеры простаивают.
Когда выбрать RabbitMQ
RabbitMQ хорош, когда:
Сложная маршрутизация: разные события нужно направлять в разные очереди по разным правилам. Exchange + routing key делает это декларативно.
Работа с задачами (task queue): Celery, фоновые воркеры, cron-задачи. Каждая задача должна выполниться ровно один раз - fair dispatch с ack/nack.
Короткоживущие события: email-уведомления, push. Не нужна история - сообщение выполнено и забыто.
Разнородные consumer-ы: PHP-воркер + Go-воркер + JS-скрипт легко подписываются на один exchange через AMQP.
Publish → exchange → queue "email" → PHP email-sender
→ queue "push" → Go push-sender
→ queue "analytics"→ JS aggregator
Когда выбрать Kafka
Kafka хороша, когда:
Несколько независимых consumer-ов: лог событий читают аналитика, аудит-сервис, email-сервис и ML-pipeline одновременно, каждый со своим offset-ом.
История событий нужна: compliance, аудит, replay при добавлении нового сервиса. Kafka хранит события дни/недели/бесконечно.
Высокий throughput: Kafka масштабируется до миллионов событий/сек добавлением партиций. RabbitMQ упирается в память раньше.
Event Sourcing: реконструкция состояния из лога событий. Kafka - идеальное хранилище.
Потоковая обработка: Kafka Streams, ksqlDB для обработки событий в реальном времени.
Сравнительная таблица
| Характеристика | RabbitMQ | Kafka |
|---|---|---|
| Модель | Очередь (push) | Лог (pull) |
| Retention | До ack | По времени/объёму |
| Routing | Exchange + routing key | По топику + partition key |
| Throughput | Тысячи/сек | Миллионы/сек |
| Replay событий | Нет | Да (по offset) |
| Несколько consumer-ов | Нет (fanout - костыль) | Да (consumer groups) |
| Порядок | Per-queue | Per-partition |
| Протокол | AMQP 0-9-1 | Kafka wire protocol |
| Go-библиотека | amqp091-go | franz-go / sarama |
| Docker image | rabbitmq:3-management | confluentinc/cp-kafka |
| ZooKeeper | Нет | KRaft (ZK устарел с 2.8) |
franz-go vs sarama
Для Kafka в Go два основных варианта:
sarama (Shopify): старейшая библиотека, огромная кодовая база, много legacy. Используется везде, где есть legacy Go-код с Kafka.
franz-go: современная библиотека, написана с нуля. Более простое API, лучшая производительность, поддерживает все современные Kafka API. Рекомендуется для новых проектов.
// franz-go: простой producer
client, _ := kgo.NewClient(
kgo.SeedBrokers("localhost:9092"),
)
defer client.Close()
record := &kgo.Record{
Topic: "lesson-events",
Key: []byte("user-42"),
Value: []byte(`{"event":"lesson.completed","lessonId":"go-01"}`),
}
client.Produce(ctx, record, nil)
Типичная ошибка
Выбирать RabbitMQ там, где нужен Kafka, и наоборот. Симптомы неправильного выбора:
- На RabbitMQ: «нам нужно replay событий» - надо Kafka
- На Kafka: «нам нужна сложная маршрутизация по 10 типам событий в разные очереди» - надо RabbitMQ или topic per event type
- На Kafka: «у нас 3 события в сутки» - RabbitMQ проще в ops
Мини-задание
- Ответь: в твоём проекте есть задачи типа «выполнить один раз» (email, webhook) - это RabbitMQ. Есть аналитика, аудит, несколько читателей - это Kafka
- Какой тип exchange подойдёт для рассылки одного события в несколько очередей?
- Если topic имеет 4 партиции, сколько воркеров в consumer group будут реально работать при 6 запущенных?
- Прочитай: event-driven - гарантии доставки - какие гарантии поддерживает каждый брокер по умолчанию?