Типы доставки сообщений
©️At most once
Продюсер отправляет сообщение и не ждет подтверждения
При сбое данные могут потеряться
➡️ пример: отправка логов, где потеря одной записи некритична
✳️как работает: продюсер не ждёт подтверждения от брокера (
acks=0), консьюмер сразу обновляет офсет©️At least once
Продюсер отправляет сообщение, ждет подтверждения. При сбое может отправить повторно, появляются дубли
➡️ платёжная система, где потеря недопустима, но дубли можно обработать
✳️ продюсер ждёт подтверждения (
acks=all), консьюмер обновляет офсет только после обработки©️Exactly once
Идеальная гарантия: без потерь и дублей. Kafka поддерживает механизм Transactional Producer
Реализуется через:
🔸Идемпотентные продюсеры (Kafka 0.11+) – подавление дублей на стороне брокера
🔸 транзакции между продюсером и консьюмером
🔸 ограничения: работает только в рамках одного кластера Kafka
➡️ обработка заказов: заказ фиксируется в БД + отправляется событие в Kafka в одной транзакции
‼️ на практике exactly once сложно обеспечить
Если Kafka сохраняет сообщение один раз, потребитель может ошибиться (например, дважды обработать запись)
Кратко
🟠At most once → без подтверждения → возможны потери
🟠 At least once → с подтверждением → возможны дубли
🟠 Exactly once → транзакции + идемпотентность → нет потерь и дублей, но дорого и сложно
Защита от дублей
При использовании At least once возможны дубли, нужно предусматривать их обработку
🔸 Индекс уникальности
Можно настроить ключи сообщений так, чтобы консьюмер сохранял только уникальные значения
- продюсер генерирует
message_id (UUID или хэш содержимого)- брокер или БД консьюмера проверяет уникальность перед записью
➡️ пример: база заказов с уникальным индексом по
order_id → повторная запись невозможна🔸 Паттерн Outbox
- при обновлении данных сервис сохраняет событие в отдельную таблицу Outbox вместе с основной записью
- фоновый процесс читает события из Outbox и отправляет их в Kafka
➡️ пример: интернет-магазин записывает заказ в основную таблицу и событие "OrderCreated" в Outbox. Затем отдельный процесс отправляет событие в Kafka
🔸 Паттерн Inbox
Используется на стороне консьюмера
- все события сохраняются в отдельную таблицу Inbox перед обработкой
- при сбое необработанные события можно переобработать без риска дублирования
➡️ пример: сервис оплаты принимает событие "OrderPaid", сохраняет его в Inbox, затем подтверждает обработку
⏪Inbox и Outbox часто применяются вместе, для обеспечения надёжности и идемпотентности при взаимодействии между микросервисами через Kafka⏩
Партиции и масштабирование
Партиция — минимальная единица хранения и обработки сообщений в Kafka
Масштабирование Kafka-кластера напрямую зависит от числа партиций
Связь: партиции ➡️ консьюмеры ➡️ сервисы:
🔵 партиция может быть одновременно прочитана только одним консьюмером в группе
🔵 консьюмер — отдельный процесс или поток приложения, читающий данные из Kafka
🔵 сервис — приложение / микросервис, который внутри себя запускает одного или несколько консьюмеров
🔵каждый экземпляр сервиса (или процесс) фактически становится одним консьюмером Kafka
❗️Важно
🔵 чтение и запись могут происходить параллельно по количеству партиций
Чем больше партиций, тем выше параллелизм обработки
🔵порядок сообщений сохраняется только внутри одной партиции
Почему важно количество партиций
©️если партиций мало → масштабировать обработку за счёт увеличения количества сервисов не получится
©️если партиций много → можно масштабировать консьюмеров горизонтально (новые инстансы будут получать работу)
📎 Материалы
1. Гарантии доставки сообщений в Kafka
2. Синхронизация асинхронности: Dead Letter и Inbox для обработки зависимых сообщений
3. Как обработать миллион сообщений из kafka
4. Под капотом продюсера Kafka: UML-диаграмма публикации сообщений
5. Kafka за 20 минут. Ментальная модель и как с ней работать
📚 Дилан Скотт, Виктор Гамов, Дейв Клейн. Kafka в действии
#интеграции
➿➿➿➿➿➿➿➿
🧑🎓 Больше полезного в базе знаний по системному анализу