Урок 07.06: Брокеры сообщений — Kafka, RabbitMQ, SQS
Цель урока
Понять фундаментальные модели работы брокеров сообщений (Queue и Log-based), научиться выбирать брокер под задачу, проектировать топики, очереди, ключи партиционирования и схемы dead letter queue. Особый фокус: что должен знать и уметь аналитик при проектировании асинхронной интеграции с брокером, чтобы не допустить фатальных ошибок (потеря сообщений, нарушение порядка, неверный выбор брокера).
Ключевые понятия
| Термин | Определение |
|---|---|
| Брокер сообщений (Message Broker) | Промежуточное ПО для асинхронной передачи сообщений между системами |
| Producer (Продюсер) | Приложение, которое отправляет сообщения в брокер |
| Consumer (Консюмер) | Приложение, которое читает сообщения из брокера |
| Topic (Топик) | Именованный канал (категория) сообщений в Kafka/Pulsar |
| Partition (Партиция) | Единица параллелизма в Kafka — упорядоченная последовательность сообщений |
| Offset | Порядковый номер сообщения внутри партиции Kafka |
| Consumer Group | Группа потребителей, совместно читающих топик (каждая партиция — одному consumer) |
| Queue (Очередь) | Канал «точка-точка»: сообщение получает ровно один consumer (RabbitMQ, SQS) |
| Exchange (Обменник) | Маршрутизатор в RabbitMQ: определяет, в какие очереди попадает сообщение |
| Routing Key | Ключ маршрутизации — строка, которую exchange сравнивает с binding |
| Binding | Правило: exchange → очередь (набор routing key patterns) |
| DLQ (Dead Letter Queue) | Очередь для «проблемных» сообщений, которые не смогли обработаться |
| Retention | Время/размер хранения сообщений в брокере (Kafka хранит долго, RabbitMQ — до ACK) |
| Replay | Возможность перечитать старые сообщения (Kafka — да, RabbitMQ — нет) |
| Consumer Lag | Отставание consumer-а от producer-а (измеряется в сообщениях или времени) |
| Rebalancing | Перераспределение партиций между consumer-ами при изменении состава группы |
| Poison Message | Сообщение, которое consumer не может обработать (снова и снова) |
| At-least-once / At-most-once / Exactly-once | Гарантии доставки (подробно в уроке 07.05) |
1. Зачем нужны брокеры сообщений?
1.1. Проблема прямой интеграции «система-система»
Представьте, что три сервиса должны обмениваться данными напрямую, без брокера:
┌──────────┐ HTTP ┌──────────┐
│ Сервис A │────────────────→│ Сервис B │
│ (Order) │ │ (Payment)│
└──────────┘ └──────────┘
│ │
│ HTTP │ HTTP
▼ ▼
┌──────────┐ ┌──────────┐
│ Сервис C │ │ Сервис D │
│ (Notify) │ │(Analytics)│
└──────────┘ └──────────┘
Проблемы такого подхода:
| Проблема | Описание | Последствие |
|---|---|---|
| Связанность (Coupling) | Сервис A знает про B, C, D — и их адреса | Изменение C (новый URL) → меняется A |
| Надёжность | Если D упал — A получает timeout и ошибку | A должен обрабатывать ошибки каждого сервиса |
| Масштабирование | При пике нагрузки A должен ждать, пока B обработает | A блокируется, растёт latency |
| Гарантии доставки | Если D упал на момент отправки — сообщение потеряно | Нет встроенного retry |
| Один ко многим | A должен разослать одно событие всем подписчикам | N HTTP-запросов из A |
| Трассировка | Нет единого места, где виден поток сообщений | Отладка — ад |
1.2. Решение: брокер сообщений
┌───────────────┐
┌──────────┐ │ │ ┌──────────┐
│ Сервис A │───────→│ │───────→│ Сервис B │
│ (Order) │ │ БРОКЕР │ │ (Payment) │
└──────────┘ │ СООБЩЕНИЙ │ └──────────┘
│ │
┌──────────┐ │ (Kafka / │ ┌──────────┐
│ Сервис A │ │ RabbitMQ / │ │ Сервис C │
│ (Order) │───────→│ SQS) │───────→│ (Notify) │
└──────────┘ │ │ └──────────┘
│ │ ┌──────────┐
│ │───────→│ Сервис D │
└───────────────┘ │(Analytics)│
└──────────┘
Что даёт брокер:
| Преимущество | Как это работает |
|---|---|
| Слабая связанность | Producer не знает consumer-ов. Он просто публикует сообщение в topic/queue |
| Надёжность | Сообщение хранится в брокере, пока consumer не подтвердит обработку |
| Буферизация | При пике нагрузки сообщения копятся в брокере, consumer обрабатывает в своём темпе |
| Один-to-многие | Одно сообщение — N подписчиков (Pub-Sub) |
| Гарантии доставки | At-least-once, at-most-once, exactly-once (effectively) |
| Retry и DLQ | Ошибочные сообщения можно повторять или отправлять в DLQ |
| Аудит и replay | Kafka хранит историю сообщений — можно перечитать |
1.3. Аналогия: брокер — почтовое отделение
Без брокера:
Вы (Producer) → идёте к каждому получателю лично →
если получателя нет дома → письмо не доставлено
С брокером (почтовое отделение):
Вы → опускаете письмо в ящик (Producer → Topic)
Почтальон (Consumer) → забирает и доставляет
Если получателя нет → письмо на почте (DLQ после N попыток)
Вы не знаете, кто и когда заберёт письмо (слабая связанность)
Письмо хранится на почте N дней (retention)
2. Две фундаментальные модели брокеров
Все брокеры сообщений делятся на два принципиально разных типа:
2.1. Message Queue Model (Очередь) — RabbitMQ, AWS SQS
Producer ──→ Queue ──→ Consumer A (получает сообщение)
──→ Consumer B (ждёт, пока A занят)
──→ Consumer C (ждёт)
Как работает:
- Одно сообщение → ровно один consumer (competitive consumption)
- После ACK — сообщение удаляется из очереди
- FIFO внутри очереди (если не настроено иначе)
- Очередь — это временный буфер (сообщения живут, пока не обработаны или не истёк TTL)
Аналогия: Касса в супермаркете. Один покупатель → один кассир. После обслуживания покупатель уходит.
Когда выбирать:
- Задача должна быть выполнена ровно один раз (send email, charge payment)
- Нужна балансировка нагрузки между воркерами
- Не нужно перечитывать историю
2.2. Log-based Model (Журнал / Event Log) — Apache Kafka, Pulsar
Producer ──→ Topic (Partition 0): [msg1, msg2, msg3, msg4, msg5, ...]
↓ ↓
Consumer Consumer
Group A Group B
(читают (читают
всё) всё)
Как работает:
- Сообщения не удаляются после чтения — хранятся N дней (retention)
- Каждый consumer group читает все сообщения независимо (Pub-Sub)
- Consumer сам управляет offset (какое сообщение читать)
- Partition — единица параллелизма и порядка
Аналогия: Лента в Instagram. Вы (producer) опубликовали пост. Все подписчики (consumer groups) увидят его независимо и в любое время (пока пост не удалён по retention).
Когда выбирать:
- Одно событие нужно нескольким сервисам (Pub-Sub)
- Нужна история сообщений (replay, аудит)
- Высокая пропускная способность (миллионы сообщений/сек)
- Важен строгий порядок внутри сущности (через ключ партиционирования)
2.3. Сравнение моделей
| Характеристика | Queue (RabbitMQ/SQS) | Log-based (Kafka/Pulsar) |
|---|---|---|
| Модель | Точка-точка | Pub-Sub + Queue (через Consumer Group) |
| Удаление сообщений | После ACK | По retention (дни/недели) |
| Порядок | Внутри очереди (FIFO) | Внутри партиции |
| Пропускная способность | Тысячи/сек | Миллионы/сек |
| Хранение | В оперативной памяти / диск (до ACK) | На диске (журнал) |
| Replay | ❌ Нет | ✅ Да (reset offset) |
| Pub-Sub | Через Exchange (RabbitMQ) | Через Consumer Group |
| Сложность | Низкая | Высокая |
| Типичный use case | Задачи (email, payment), worker distribution | Event-driven архитектура, аудит, аналитика |
Для аналитика: Это самый важный выбор в проектировании асинхронной интеграции. Kafka — не «улучшенный RabbitMQ». Это принципиально другая модель. Kafka хранит всё, RabbitMQ — только пока не обработано.
3. Apache Kafka — глубокое погружение
3.1. Базовая архитектура
┌───────────────────┐
│ ZooKeeper / │
│ KRaft (metadata)│
└────────┬──────────┘
│
┌──────────┐ │ ┌──────────┐
│ Producer │─────────────────┼─────────→│ Kafka │
│(Сервис А)│ │ │ Cluster │
└──────────┘ │ │ │
│ │ ┌──────┐ │
┌──────────┐ │ │ │Part.0 │ │
│ Producer │─────────────────┼─────────→│ │Part.1 │ │
│(Сервис Б)│ │ │ │Part.2 │ │
└──────────┘ │ │ └──────┘ │
│ └────┬─────┘
│
┌────────────────────┼────────────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ Consumer │ │ Consumer │ │ Consumer │
│ Group A │ │ Group B │ │ Group C │
│ (Service) │ │(Analytics) │ │ (Audit) │
└───────────┘ └───────────┘ └───────────┘
3.2. Topic, Partition, Offset — «Святая Троица» Kafka
Topic — именованный канал сообщений (например, order.events, task.events).
Partition — единица параллелизма. Topic может иметь N партиций. Сообщения распределяются по партициям по ключу.
Topic: order.events (3 партиции)
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Partition 0 │ │ Partition 1 │ │ Partition 2 │
│ offset: 0..999 │ │ offset: 0..999 │ │ offset: 0..999 │
│ key: order_1 │ │ key: order_2 │ │ key: order_5 │
│ key: order_4 │ │ key: order_3 │ │ key: order_7 │
│ ... │ │ ... │ │ ... │
│ СТРОГИЙ ПОРЯДОК │ │ СТРОГИЙ ПОРЯДОК │ │ СТРОГИЙ ПОРЯДОК │
│ ВНУТРИ partition │ │ ВНУТРИ partition │ │ ВНУТРИ partition │
└──────────────────┘ └──────────────────┘ └──────────────────┘
Offset — порядковый номер сообщения внутри партиции. Consumer запоминает offset, до которого он прочитал.
Consumer читает Partition 0:
offset 0: order.created {orderId: 1, status: "new"}
offset 1: payment.processed {orderId: 1, status: "paid"} ← Consumer сейчас здесь (offset=1)
offset 2: order.confirmed {orderId: 1, status: "confirmed"}
offset 3: order.shipped {orderId: 1, status: "shipped"}
3.3. Ключ партиционирования — самый важный проектный выбор
// Producer отправляет сообщение с ключом
producer.send(new ProducerRecord<>("order.events",
orderId, // КЛЮЧ (определяет partition)
event // СООБЩЕНИЕ
));
// partition = hash(key) % numberOfPartitions
Таблица правильных и неправильных ключей:
| Ключ | Правильно? | Результат |
|---|---|---|
orderId (UUID заказа) |
✅ | Все события по одному заказу — в одной партиции → строгий порядок |
taskId (UUID задачи) |
✅ | Все изменения задачи — последовательно |
userId |
✅ | Все события пользователя — по порядку |
random UUID |
❌ | Каждое сообщение — в случайную партицию → порядок не гарантирован |
eventType («created», «updated») |
❌ | Все created — в Partition 0, все deleted — в Partition 1. Deleted может прийти раньше created |
null |
❌ | Round-Robin распределение → никакого порядка |
3.4. Consumer Group — масштабирование потребления
Topic: order.events (4 партиции)
Consumer Group "order-service" (2 consumers):
Consumer A ← Partition 0, Partition 1
Consumer B ← Partition 2, Partition 3
Consumer Group "analytics" (4 consumers):
Consumer C ← Partition 0
Consumer D ← Partition 1
Consumer E ← Partition 2
Consumer F ← Partition 3
Consumer Group "audit" (1 consumer):
Consumer G ← Partition 0, Partition 1, Partition 2, Partition 3
(все партиции — одному consumer-у)
Правила:
- Одна партиция → одному consumer-у в группе (нельзя 2 consumer-ам читать 1 партицию)
- Если consumer-ов больше, чем партиций — лишние простаивают
- Если consumer-ов меньше — один consumer читает несколько партиций
- Разные Consumer Group читают независимо (каждая со своим offset)
Что должен спроектировать аналитик:
| Параметр | Вопрос | Пример |
|---|---|---|
| Количество партиций | Сколько сообщений в секунду? | 100 msg/s → 3-6 партиций |
| Количество consumer-ов | Сколько инстансов сервиса? | 3 инстанса → 3+ партиции |
| Retention | Как долго хранить сообщения? | 7 дней (или 30 для аудита) |
| Ключ | Как гарантировать порядок? | orderId |
3.5. Retention и Compaction
Retention — политика хранения сообщений:
| Политика | Параметр | Пример |
|---|---|---|
| По времени | retention.ms |
7 дней (604800000 ms) |
| По размеру | retention.bytes |
10 GB на партицию |
| Compact (по ключу) | cleanup.policy=compact |
Хранить только последнее сообщение по каждому ключу |
Log Compaction — важная фича для аналитика:
До compaction:
Partition 0:
offset 0: {userId: 1, email: "old@mail.com"}
offset 1: {userId: 2, email: "test@mail.com"}
offset 2: {userId: 1, email: "new@mail.com"} ← новое значение для userId=1
После compaction (cleanup.policy=compact):
Partition 0:
offset 1: {userId: 2, email: "test@mail.com"}
offset 2: {userId: 1, email: "new@mail.com"} ← осталось только последнее значение
(сообщение с offset=0 удалено — устаревшее)
Где используется: Таблица «user profile» в Kafka — храним только последний профиль каждого пользователя. Новый consumer получает только актуальные данные.
3.6. Replication и ISR (In-Sync Replicas)
Kafka — распределённая система. Каждая партиция реплицируется на N брокеров:
Topic: order.events, Partition 0
┌─────────────────────────────────────────────┐
│ Брокер 1 (Leader) │
│ Partition 0: [msg1, msg2, msg3] │
│ ↓ репликация │
│ Брокер 2 (Follower, in ISR) │
│ Partition 0: [msg1, msg2, msg3] │
│ ↓ репликация │
│ Брокер 3 (Follower, in ISR) │
│ Partition 0: [msg1, msg2, msg3] │
└─────────────────────────────────────────────┘
ISR (In-Sync Replicas) — реплики, которые «успевают» за лидером.
Настройка durability (acks):
| acks | Что означает | Риск | Производительность |
|---|---|---|---|
0 |
Producer не ждёт подтверждения | ❌ Потеря сообщения при сбое | 🚀 Максимальная |
1 |
Ждёт подтверждения от лидера | ⚠️ Потеря при падении лидера до репликации | 🟡 Средняя |
all (-1) |
Ждёт подтверждения от всех ISR | ✅ Нет потери | 🐢 Минимальная |
Для аналитика: В требованиях к интеграции указывайте
acks=allдля критичных данных (заказы, платежи) иacks=1для менее критичных (логи, метрики).
3.7. Когда Kafka — плохой выбор
| Сценарий | Почему Kafka не подходит | Что выбрать |
|---|---|---|
| Нужна отложенная задача на 24 часа (Scheduled) | Kafka не умеет откладывать сообщения | RabbitMQ (TTL) + DLQ, или SQS (Delay Queue) |
| Нужно гарантировать exactly-once к конкретному моменту | Kafka — at-least-once + идемпотентность (effectively once) | RabbitMQ + dedup, или транзакционная БД |
| Сообщений мало (10/день) и не нужен replay | Kafka — избыточен (кластер из 3 брокеров) | RabbitMQ, SQS |
| Нужна строгая глобальная FIFO (все сообщения по порядку) | Kafka — порядок только внутри партиции | RabbitMQ (одна очередь, один consumer) |
| Нужна сложная маршрутизация (по content-based routing) | Kafka — только по ключу в партицию | RabbitMQ (Exchange с routing key) |
| Команда маленькая, нет опыта администрирования Kafka | Kafka — сложный в эксплуатации | Управляемые сервисы (Confluent Cloud, Redpanda) или SQS |
4. RabbitMQ — глубокое погружение
4.1. Базовая архитектура
RabbitMQ основан на модели Exchange-Queue-Binding:
Producer ──→ Exchange ──(binding)──→ Queue ──→ Consumer
│ │
│ (если не обработано)
│ │
└──(routing key)──→ DLQ (Dead Letter Queue)
Компоненты:
| Компонент | Описание | Аналог в Kafka |
|---|---|---|
| Exchange | Маршрутизатор: получает сообщение от producer и отправляет в очередь(и) | Topic (но в Kafka producer пишет напрямую в partition) |
| Binding | Правило: exchange → очередь (с routing key) | — |
| Queue | Очередь сообщений (FIFO) | Partition (но queue — временная, partition — постоянная) |
| Routing Key | Строка, по которой exchange решает, в какую очередь отправить | Ключ партиционирования |
| Consumer | Приложение, читающее из очереди | Consumer Group |
| DLQ | Очередь для «плохих» сообщений | — (в Kafka — через consumer retry topic) |
4.2. Exchange Types — четыре типа маршрутизации
Direct Exchange — точное совпадение routing key
Producer → Exchange: direct → Queue A (routing key = "error")
→ Queue B (routing key = "warning")
→ Queue C (routing key = "info")
Сообщение с routing key = "error" → только в Queue A
Сообщение с routing key = "warning" → только в Queue B
Когда использовать: Логи (error → в алерт-систему, info → в архив), распределение задач по типу.
Topic Exchange — маска routing key (wildcard)
Producer → Exchange: topic → Queue A (routing key = "logs.error.*")
→ Queue B (routing key = "logs.#")
Сообщение с routing key = "logs.error.db" → Queue A + Queue B
Сообщение с routing key = "logs.info.api" → Queue B (только B, не подходит под A)
# — любое количество слов (logs.# = logs.error.db, logs.info.api, logs.warning...)
* — ровно одно слово (logs.error.* = logs.error.db, НО НЕ logs.error.db.mysql)
Когда использовать: Маршрутизация событий по иерархическому ключу (order.created, order.payment.processed).
Fanout Exchange — broadcast (всем подписчикам)
Producer → Exchange: fanout → Queue A
→ Queue B
→ Queue C
Каждое сообщение → во ВСЕ привязанные очереди
Routing key игнорируется
Когда использовать: Pub-Sub: уведомления, события (все сервисы должны узнать о task.created).
Headers Exchange — маршрутизация по заголовкам (не routing key)
Producer → Exchange: headers → Queue A (x-match = any, header1 = "value1")
→ Queue B (x-match = all, header1 = "value1", header2 = "value2")
Сообщение с headers: {header1: "value1", header3: "value3"} → Queue A (совпал 1 заголовок из 1)
Сообщение с headers: {header1: "value1", header2: "value2"} → Queue A + Queue B
Когда использовать: Сложная context-based маршрутизация (почти не используется на практике).
4.3. Таблица выбора Exchange
| Exchange Type | Маршрутизация по | Пример binding | Когда выбрать |
|---|---|---|---|
| Direct | Точное совпадение routing key | "error" → Queue A |
Распределение по типам/категориям |
| Topic | Маска routing key (wildcard) | "logs.#" → Queue A |
Иерархическая маршрутизация |
| Fanout | Всем (broadcast) | — | Широковещательные события |
| Headers | По заголовкам (key=value) | x-match=all |
Сложные условия (редко) |
4.4. Dead Letter Queue (DLQ) — что должен знать аналитик
Зачем: Если consumer не может обработать сообщение (невалидные данные, ошибка внешней системы), RabbitMQ может отправить его в DLQ.
Как настроить:
Очередь: tasks.queue
DLX: tasks.dlx (Dead Letter Exchange)
DLQ: tasks.dlq (Dead Letter Queue)
Жизненный цикл сообщения:
1. Producer → Exchange → tasks.queue
2. Consumer пытается обработать → выбрасывает исключение
3. Сообщение отклоняется (basic.nack) → попадает в tasks.dlx
4. tasks.dlx → tasks.dlq
5. Из DLQ можно переотправить (re-publish) или анализировать
Причины попадания в DLQ:
| Причина | Описание | Действие аналитика |
|---|---|---|
| Consumer отклонил (nack) | channel.basicNack(deliveryTag, false, false) |
Проверить, почему consumer не может обработать |
| Истёк TTL | x-message-ttl превышен |
Увеличить TTL или сообщение испорчено |
| Очередь переполнена | x-max-length превышен |
Увеличить лимит или распределить нагрузку |
| Binding не найден | Нет очереди для routing key | Проверить настройки exchange |
Что делать с сообщениями в DLQ:
- Анализировать — понять причину (невалидные данные? баг?)
- Переотправить — исправить проблему и повторно опубликовать
- Автоматический retry — Consumer может проверить, не пора ли переотправить из DLQ
- Dead Letter Sink — сохранить в хранилище (S3, БД) для разбора
4.5. Publisher Confirms и Consumer ACK
Publisher Confirms — гарантия, что сообщение дошло до брокера:
Producer → RabbitMQ → Confirm (ACK from broker)
Producer знает: сообщение получено брокером (написано на диск)
Без confirms: producer не знает, дошло ли сообщение (может потеряться)
Consumer ACK — гарантия, что consumer обработал сообщение:
RabbitMQ → Consumer → ACK (сообщение удаляется из очереди)
Consumer → NACK (сообщение → DLQ или возврат в очередь)
Consumer → timeout без ACK (сообщение переотправляется другому consumer-у)
Режимы ACK:
| Режим | Когда ACK | Риск | Производительность |
|---|---|---|---|
| auto | Автоматически при получении | ❌ Потеря, если consumer упал до обработки | 🚀 Высокая |
| manual | Явный вызов basicAck() после обработки |
✅ Нет потери | 🐢 Средняя |
Для аналитика: В требованиях обязательно указывайте
manual ACKдля критичных интеграций.auto ACKдопустим только для логов и мониторинга.
4.6. Когда RabbitMQ — плохой выбор
| Сценарий | Почему RabbitMQ не подходит | Что выбрать |
|---|---|---|
| Нужно хранить историю сообщений (аудит, replay) | Сообщения удаляются после ACK | Kafka |
| Пропускная способность > 100K msg/s | RabbitMQ упирается в Erlang VM | Kafka, Pulsar |
| Нужна строгая упорядоченность по ключу | FIFO в одной очереди — да, но сложно с routing | Kafka (partition по ключу) |
| Очень простое приложение (одна очередь) | RabbitMQ — тяжеловат | SQS (управляемый) |
| Сообщения > 100 MB (файлы, изображения) | RabbitMQ не оптимизирован для больших сообщений | S3 + ссылка в сообщении |
5. Другие брокеры (обзорно)
5.1. AWS SQS + SNS
SQS (Simple Queue Service):
- Управляемая очередь (без администрирования)
- Модель: Queue (точка-точка)
- Стандартная очередь: at-least-once, порядок не гарантирован
- FIFO очередь: exactly-once, строгий порядок (но 3000 msg/s max)
- Лучшее решение, когда не хотите администрировать брокер
SNS (Simple Notification Service):
- Pub-Sub (Topic)
- Подписчики: SQS, Lambda, Email, SMS, HTTP
- Часто используется как SNS → SQS (Pub-Sub → Queue)
Паттерн SNS + SQS:
Producer → SNS Topic → SQS Queue A → Consumer A
→ SQS Queue B → Consumer B
→ SQS Queue C → Consumer C
Когда выбирать:
- Вы уже в AWS
- Не хотите администрировать Kafka/RabbitMQ
- Нагрузка до десятков тысяч msg/s
5.2. NATS
- Лёгкий, быстрый (10M msg/s на одном сервере)
- Аналогия: UDP для сообщений
- Модель: Pub-Sub, Queue Groups
- Гарантии: at-most-once (по умолчанию)
- НЕ хранит сообщения (если consumer отключён — потеря)
- Используется в: IoT, real-time, микросервисы (NATS JetStream добавляет хранение)
5.3. Redis Pub-Sub / Redis Streams
Redis Pub-Sub:
- Крайне быстрый (in-memory)
- Не хранит сообщения — если consumer отключён в момент публикации, сообщение потеряно
- Только at-most-once
Redis Streams (с 5.0):
- Хранит сообщения (как Kafka, но in-memory)
- Consumer Groups
- Хорош для: кэш + очередь в одном (но не для тысяч сообщений)
5.4. Pulsar / Redpanda
Apache Pulsar:
- Двухуровневая архитектура: BookKeeper (хранение) + Broker (обработка)
- Separate storage and compute
- Поддержка Geo-Replication из коробки
Redpanda:
- Kafka API-совместимый (можно использовать Kafka-клиенты)
- Быстрее Kafka (нет JVM, нет ZooKeeper)
- Меньше задержка (p99)
5.5. Сводная таблица брокеров
| Брокер | Модель | Хранение | Порядок | Произв. | Сложность | Управление |
|---|---|---|---|---|---|---|
| Kafka | Log | Диск (retention) | Внутри партиции | 🚀 Миллионы/сек | 🔴 Высокая | Self-managed / Cloud |
| RabbitMQ | Queue | Память/диск (до ACK) | Внутри очереди | 🟡 Десятки тыс/сек | 🟡 Средняя | Self-managed / Cloud |
| AWS SQS | Queue | AWS | FIFO или нет | 🟢 Сотни тыс/сек | 🟢 Низкая | Управляемый |
| AWS SNS | Pub-Sub | Нет | Нет | 🟢 Сотни тыс/сек | 🟢 Низкая | Управляемый |
| NATS | Pub-Sub | Нет (есть JetStream) | Нет | 🚀 Миллионы/сек | 🟢 Низкая | Self-managed |
| Redis | Pub-Sub / Stream | Память (опционально) | Внутри потока | 🚀 In-memory | 🟢 Низкая | Self-managed / Cloud |
| Pulsar | Log | Диск (BookKeeper) | Внутри партиции | 🚀 Миллионы/сек | 🔴 Высокая | Self-managed / Cloud |
| Redpanda | Log | Диск (без JVM) | Внутри партиции | 🚀 Миллионы/сек | 🟡 Средняя | Self-managed / Cloud |
6. Как аналитику выбирать брокера
6.1. Алгоритм выбора
┌──────────────────────────────────────────────────────────────────┐
│ 1. Нужна ли история сообщений (replay, аудит)? │
│ Да ──→ Нужен ли cloud? │
│ Да ──→ Confluent Kafka / AWS MSK / Redpanda Cloud │
│ Нет ──→ Kafka / Pulsar / Redpanda │
│ │ │
│ Нет ─→ 2. Нужна сложная маршрутизация? │
│ Да ──→ RabbitMQ (Exchange + routing key) │
│ │ │
│ Нет ─→ 3. Нужно минимум операций (управляемый)? │
│ Да ──→ AWS SQS (+ SNS для pub-sub) │
│ Нет ──→ RabbitMQ │
│ │
│ Дополнительные фильтры: │
│ - Пропускная способность < 10K msg/s → любой │
│ - Пропускная способность > 100K msg/s → Kafka / Pulsar │
│ - Строгий порядок по сущности → Kafka (partition key) │
│ - Отложенные задачи (delay) → RabbitMQ (TTL) / SQS (delay) │
│ - Уже используете AWS/GCP/Azure → их сервисы │
└──────────────────────────────────────────────────────────────────┘
6.2. Что должно быть в требованиях к брокеру
| Параметр | Пример требования | Почему важно |
|---|---|---|
| Модель | Queue (точка-точка) или Pub-Sub | Определяет, сколько consumer-ов получат сообщение |
| Гарантии доставки | At-least-once | Определяет, могут ли быть дубликаты |
| Порядок сообщений | По ключу orderId внутри партиции |
Определяет бизнес-корректность |
| Retention | 7 дней | Как долго можно перечитывать |
| Пропускная способность | 500 msg/s average, 2000 msg/s peak | Определяет выбор брокера |
| Размер сообщения | Средний 5 KB, макс 50 KB | Определяет, не нужен ли S3 для больших файлов |
| Replay | Возможность перечитать сообщения за последние 7 дней | Нужно ли для аудита / восстановления |
| DLQ | Необработанные сообщения — в DLQ, алерт при > 10 в DLQ | Обработка ошибок |
| Мониторинг | Consumer lag < 1000 сообщений | Обнаружение проблем |
| Managed / Self | Managed (AWS MSK) | Кто администрирует |
6.3. Практический пример выбора
Кейс: Интернет-магазин
| Интеграция | Брокер | Почему |
|---|---|---|
| Уведомление о новом заказе (email + SMS) | RabbitMQ | Точка-точка (один email, одно SMS). Нужна маршрутизация (email → Email Queue, SMS → SMS Queue) |
| События заказа (created, paid, shipped) | Kafka | Pub-Sub: аналитика, аудит, склад, уведомления. Нужна история для replay. Важен порядок по orderId |
| Обработка изображений товаров | SQS | Простая очередь задач. Нет порядка. Нужен managed (не хотим админить) |
| Метрики и мониторинг | Kafka | Высокая пропускная способность (миллионы событий). At-most-once (потеря метрики не страшна) |
7. Dead Letter Queue (DLQ) — проектирование для аналитика
7.1. Общая схема DLQ
┌──────────┐
│ Producer │
└────┬─────┘
│
▼
┌───────────────┐
│ Main Queue │
│ (tasks.queue) │
└───────┬───────┘
│
┌──────────┴──────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Consumer OK │ │ Consumer │
│ → ACK │ │ FAIL │
│ → удаление │ │ → NACK │
└──────────────┘ └──────┬───────┘
│
▼
┌───────────────┐
│ DLQ │
│ (tasks.dlq) │
└───────┬───────┘
│
┌───────────┴───────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Ручной разбор │ │ Автоматический│
│ (Dashboard) │ │ retry через N│
└──────────────┘ │ минут │
└──────────────┘
7.2. Параметры DLQ в RabbitMQ
| Параметр | Описание | Пример |
|---|---|---|
x-dead-letter-exchange |
Exchange, куда отправлять «мёртвые» сообщения | tasks.dlx |
x-dead-letter-routing-key |
Routing key для DLQ | tasks.dlq |
x-message-ttl |
Время жизни сообщения в основной очереди | 60000 (1 минута) |
x-max-retries |
Количество попыток перед DLQ (через DLQ + republish) | 3 |
7.3. Шаблон «Retry with DLQ» для аналитика
Требования к DLQ (Retry Policy):
1. Consumer пытается обработать сообщение
2. Если ошибка временная (timeout, 503) —
отклонить с requeue=false → сообщение в DLQ
3. Из DLQ сообщение возвращается в основную очередь через 60 секунд
4. Максимум 3 попытки (retry count в заголовке сообщения)
5. После 3 неудач — сообщение остаётся в DLQ для ручного разбора
6. Алерт: если в DLQ больше 10 сообщений — уведомление дежурному
7.4. Poison Message — что делать
Poison Message — сообщение, которое consumer не может обработать никогда (невалидный JSON, отсутствующий ID сущности).
Сценарий без DLQ:
Consumer → NACK → возврат в очередь → снова NACK → бесконечный цикл
Сценарий с DLQ:
Consumer → NACK → в DLQ → алерт → дата-аналитик исправляет данные →
переотправка из DLQ → success
Как аналитик должен описать обработку Poison Messages:
Req-INT-005: Обработка Poison Messages
Если consumer не может обработать сообщение после 3 попыток:
1. Сообщение помещается в DLQ
2. Генерируется алерт в мониторинг (Slack / PagerDuty)
3. Сообщение хранится в DLQ 30 дней
4. Дата-аналитик вручную анализирует и исправляет данные
5. После исправления — сообщение переотправляется в основную очередь
8. Типовые проблемы с брокерами и их диагностика
8.1. Consumer Lag
Проблема: Consumer отстаёт от Producer. Сообщения накапливаются.
Producer пишет: 100 msg/s
Consumer читает: 10 msg/s
Lag: 90 msg/s → очередь растёт → задержка обработки (latency растёт)
Причины:
- Consumer слишком медленный (N+1 запросы, блокировки)
- Слишком мало consumer-ов в группе
- Слишком мало партиций (для Kafka)
- Внешняя система (API, БД) тормозит
Что проверять аналитику:
- Kafka:
kafka-consumer-groups --group order-service --describe→ LAG - RabbitMQ: Management UI → Queues → Ready + Unacknowledged
- SQS: CloudWatch → ApproximateNumberOfMessagesVisible
Когда это критично:
- Уведомления в реальном времени (Lag > 1 минуты — неприемлемо)
- Платежи (Lag > несколько секунд — потеря клиентов)
- Некритично: аналитика, отчёты (Lag может быть часами)
8.2. Consumer Rebalancing
Проблема: В Kafka при изменении состава Consumer Group (новый consumer, падение, остановка) происходит rebalance — перераспределение партиций.
До rebalance:
Consumer A: Partition 0, Partition 1
Consumer B: Partition 2, Partition 3
Consumer A упал → REBALANCE → все consumer-ы останавливаются на несколько секунд
После rebalance:
Consumer B: Partition 0, Partition 1, Partition 2, Partition 3 (все партиции)
Последствия:
- Все consumer-ы в группе останавливаются на время rebalance
- Сообщения не обрабатываются в этот период
- После rebalance — Consumer B читает всё с начала (или с последнего commit offset)
Для аналитика: Частые rebalance — симптом нестабильности. В требованиях укажите session.timeout.ms и heartbeat.interval.ms.
8.3. Потеря сообщений — причины и предотвращение
| Причина | Где | Как предотвратить |
|---|---|---|
| Producer не получил ACK → думает, что отправил | Kafka: acks=0 |
Использовать acks=all |
| Consumer auto-ACK до обработки | RabbitMQ: auto ACK | Использовать manual ACK |
| Сообщение удалено по retention до обработки | Kafka | Увеличить retention, мониторить lag |
| Rebalance при несохранённом offset | Kafka | Сохранять offset после обработки |
| Exchange не нашёл очередь | RabbitMQ | Настроить Alternate Exchange или Publisher Confirm |
| SQS — Standard queue (дубликаты) | SQS | Идемпотентность на стороне consumer |
| Сетевая потеря при publish | Любой | Retry + idempotency key |
9. Моделирование брокеров в BPMN и Sequence Diagrams
9.1. Sequence Diagram с Kafka
@startuml
actor Клиент as Client
participant "Order Service" as OrderService
collections "Kafka: order.events" as Kafka #LightBlue
participant "Payment Service" as PaymentService
participant "Notification Service" as NotificationService
Client -> OrderService: POST /orders (создать заказ)
activate OrderService
OrderService -> OrderService: Создать заказ в БД
OrderService -> Kafka: publish(order.created, key=orderId)
note right: partition = hash(orderId) % N
deactivate OrderService
== Асинхронная обработка ==
Kafka -> PaymentService: consume(order.created)
activate PaymentService
PaymentService -> PaymentService: Списать средства
PaymentService -> Kafka: publish(payment.processed, key=orderId)
deactivate PaymentService
Kafka -> NotificationService: consume(payment.processed)
activate NotificationService
NotificationService -> Client: Email: "Заказ оплачен"
deactivate NotificationService
@enduml
9.2. Sequence Diagram с RabbitMQ
@startuml
actor "Менеджер" as Manager
participant "CRM" as CRM
collections "RabbitMQ: Exchange" as RMQ #LightBlue
queue "Email Queue" as EmailQueue
queue "SMS Queue" as SMSQueue
participant "Email Worker" as EmailWorker
participant "SMS Worker" as SMSWorker
Manager -> CRM: Создать клиента
activate CRM
CRM -> CRM: Сохранить в БД
CRM -> RMQ: publish(routingKey="notification.welcome")
note right: Topic Exchange: notification.#
deactivate CRM
RMQ -> EmailQueue: binding: notification.#
RMQ -> SMSQueue: binding: notification.#
EmailQueue -> EmailWorker: deliver
activate EmailWorker
EmailWorker -> Manager: Email: Добро пожаловать!
deactivate EmailWorker
SMSQueue -> SMSWorker: deliver
activate SMSWorker
SMSWorker -> Manager: SMS: Добро пожаловать!
deactivate SMSWorker
@enduml
9.3. BPMN с брокером
[Получен заказ] → [Создать заказ] → [Отправить событие в Kafka]
│
│ topic: order.events
▼
[Параллельный шлюз]
┌────────┴────────┐
│ │
[Списать средства] [Отправить уведомление]
│ │
▼ ▼
[Заказ оплачен] [Уведомление отправлено]
│ │
└────────┬─────────┘
▼
[Подтвердить заказ]
Задание для аналитика: Нарисовать BPMN-процесс с асинхронными шагами через брокер, указав топики/очереди на соединительных линиях.
10. Чек-лист аналитика при проектировании с брокером
| № | Проверка | Где |
|---|---|---|
| 1 | Выбрана модель: Queue или Log-based? | |
| 2 | Выбран брокер под задачу (Kafka/RabbitMQ/SQS)? | |
| 3 | Kafka: определён ключ партиционирования (по сущности)? | |
| 4 | Kafka: определено количество партиций (≥ кол-ва consumer-ов)? | |
| 5 | Kafka: retention (сколько дней хранить)? | |
| 6 | Kafka: acks (0/1/all) для Producer? | |
| 7 | RabbitMQ: выбран тип Exchange (Direct/Topic/Fanout)? | |
| 8 | RabbitMQ: настроен binding (routing key)? | |
| 9 | RabbitMQ: manual ACK для критичных операций? | |
| 10 | Гарантии доставки: At-least-once / At-most-once? | |
| 11 | Идемпотентность: consumer dedup по messageId / eventId? | |
| 12 | DLQ: настроена Dead Letter Queue? | |
| 13 | DLQ: сколько retry-попыток, TTL? | |
| 14 | DLQ: алерт при N сообщений в DLQ? | |
| 15 | Poison messages: что делать с необрабатываемыми? | |
| 16 | Мониторинг: consumer lag, алерты? | |
| 17 | Размер сообщения: не превышает лимиты брокера? | |
| 18 | Security: шифрование в transit (TLS) + auth? | |
| 19 | Schema: формат сообщения описан (AVRO, JSON Schema)? | |
| 20 | Versioning: schema evolution (как менять формат)? |
Вопросы для самопроверки
Базовый уровень
- Зачем нужен брокер сообщений? Какие проблемы он решает по сравнению с прямой HTTP-интеграцией?
- В чём фундаментальная разница между Queue (RabbitMQ) и Log-based (Kafka)? Какая модель позволяет перечитывать историю?
- Что такое partition в Kafka? Почему ключ партиционирования важен для порядка сообщений?
- Какие четыре типа Exchange есть в RabbitMQ? Какой тип используется для broadcast (всем подписчикам)?
- Что такое Dead Letter Queue (DLQ)? Зачем она нужна?
Продвинутый уровень
- Kafka partition count: Аналитик спроектировал топик с 1 партицией и 10 consumer-ами. В чём проблема? Сколько партиций нужно?
- Order guarantee: Почему
randomUUIDв качестве ключа партиционирования — фатальная ошибка для бизнеса, требующего порядка? - Replay: Вам нужно восстановить состояние системы на момент 3 дня назад, потому что баг испортил данные. Какой брокер (Kafka или RabbitMQ) позволит это сделать? Как?
- DLQ проектирование: Consumer падает с ошибкой при обработке сообщения. Опишите пошагово жизненный цикл сообщения: от публикации до DLQ. Какие конфигурации RabbitMQ для этого нужны?
- Выбор брокера: Спортивная ставка. Вам нужно: 1) высокая пропускная способность (>100K msg/s матчей), 2) порядок по matchId, 3) хранение 30 дней для аудита, 4) at-least-once. Какой брокер выберете?
Практическое задание
Задание 1. Выбор модели и брокера (2 балла)
Для каждого сценария выберите модель (Queue / Log-based) и конкретного брокера (Kafka / RabbitMQ / SQS). Обоснуйте.
| № | Сценарий | Модель | Брокер | Обоснование |
|---|---|---|---|---|
| 1 | Отправка приветственного email после регистрации | |||
| 2 | События изменения статуса заказа для аналитики и аудита | |||
| 3 | Обработка загруженных изображений (ресайз, watermark) | |||
| 4 | Поток котировок акций (1000 обновлений/сек) | |||
| 5 | Уведомление о новой задаче в Telegram + Slack + Email |
Задание 2. Проектирование Kafka-топика (3 балла)
Вы проектируете систему доставки еды. При создании заказа нужно:
- Уведомить ресторан (новый заказ)
- Найти курьера
- Списать средства с карты клиента
- Отправить подтверждение клиенту (email + push)
- Записать событие в аудит
Требуется: 2.1. (1 балл) Спроектировать Kafka-топик: название, количество партиций, ключ партиционирования, retention. 2.2. (1 балл) Определить Consumer Group для каждого сервиса. Сколько consumer-ов в каждой группе? 2.3. (1 балл) Какие гарантии доставки (acks) для каждого типа сообщений?
Задание 3. RabbitMQ Exchange + Binding (2 балла)
Система мониторинга отправляет события с routing key:
monitoring.cpu.highmonitoring.memory.criticalmonitoring.disk.warningmonitoring.disk.criticalmonitoring.network.latency.high
Нужно:
- Queue A: все события с уровнем "critical" (любая категория)
- Queue B: все события с категорией "disk" (любой уровень)
- Queue C: все события "monitoring.network.latency.high" (точно)
3.1. (1 балл) Какой тип Exchange выбрать и какие binding настроить? 3.2. (1 балл) В Queue A пришло сообщение, которое consumer не смог обработать. Опишите конфигурацию DLQ для Queue A (названия exchange, routing key, количество retry).
Задание 4. Диагностика проблемы (2 балла)
Кейс: В системе бронирования отелей используется Kafka. Топик booking.events (3 партиции) с ключом bookingId. Consumer Group notification-service (3 инстанса).
В один момент перестали приходить уведомления о подтверждении бронирований. При проверке:
kafka-consumer-groups --group notification-service --describe
GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG
notification-service booking.events 0 1500 1520 20
notification-service booking.events 1 500 510 10
notification-service booking.events 2 0 10 10
4.1. (1 балл) Проанализируйте вывод. Какая партиция вызывает беспокойство? Почему? 4.2. (0,5 балла) Какие возможные причины проблемы с этой партицией? 4.3. (0,5 балла) Что вы сделаете как аналитик: какие вопросы зададите разработчикам/архитектору?
Задание 5. DLQ-стратегия (1 балл)
Спроектируйте DLQ-стратегию для сервиса уведомлений (email + SMS):
- При регистрации пользователя отправляется приветственное письмо
- Email-сервис (consumer) вызывает внешний SMTP-сервер, который иногда отвечает 5xx (временная ошибка)
- При невалидном email (формат abc@def) — письмо отправить нельзя никогда
Опишите:
- Какой брокер и почему
- Параметры DLQ (количество retry, TTL, алерт)
- Что делать с невалидными email (poison messages)
- Как обрабатывать временные ошибки SMTP
Критерии оценки
| Задание | Баллы |
|---|---|
| Задание 1: Выбор модели и брокера | 2 |
| Задание 2: Проектирование Kafka-топика | 3 |
| Задание 3: RabbitMQ Exchange + Binding | 2 |
| Задание 4: Диагностика проблемы | 2 |
| Задание 5: DLQ-стратегия | 1 |
| Итого | 10 |
Справочные материалы
Kafka: ключевые параметры для аналитика
| Параметр | Описание | Типичное значение |
|---|---|---|
num.partitions |
Количество партиций в топике | 3-12 (зависит от нагрузки) |
retention.ms |
Время хранения сообщений | 604800000 (7 дней) |
retention.bytes |
Максимальный размер на партицию | -1 (без ограничения) |
cleanup.policy |
delete / compact | delete (для событий) |
acks |
Подтверждение записи | all (для критичных) |
min.insync.replicas |
Минимум in-sync реплик | 2 |
RabbitMQ: ключевые параметры для аналитика
| Параметр | Описание | Типичное значение |
|---|---|---|
x-message-ttl |
TTL сообщения в очереди | 60000 (60 сек) |
x-max-length |
Максимум сообщений в очереди | 10000 |
x-dead-letter-exchange |
DLX для необработанных | tasks.dlx |
x-dead-letter-routing-key |
Routing key для DLQ | tasks.dlq |
x-max-priority |
Приоритеты сообщений | 10 |
Инструменты
| Инструмент | Назначение |
|---|---|
Kafka CLI (kafka-topics, kafka-console-producer, kafka-console-consumer, kafka-consumer-groups) |
Управление топиками, просмотр сообщений, мониторинг lag |
| Kafka UI (AKHQ, Kafdrop, Confluent Control Center) | Web-интерфейс для Kafka |
| RabbitMQ Management UI | Web-интерфейс (очереди, exchange, binding, мониторинг) |
| SQS Console (AWS) | Управление очередями, просмотр сообщений |
| Offset Explorer (бывший Kafka Tool) | Десктопный клиент для Kafka |