Получить доступ

Spring Kafka

KafkaTemplate, @KafkaListener, listener containers, offset ack, error handling, retries и DLT.

Producer: KafkaTemplate

KafkaTemplate — high-level API Spring Kafka для отправки records. Он скрывает низкоуровневый producer lifecycle и интегрируется с сериализацией, transactions и callbacks.

В production обычно явно задают key, topic, headers и correlation id. Key нужен не только для routing, но и для порядка событий одной бизнес-сущности.

kafkaTemplate.send("orders.events", orderId, new OrderPaid(orderId));

Consumer: @KafkaListener

@KafkaListener создаёт listener method, а listener container управляет poll loop, threads, assignment, commits и error handling.

Concurrency у listener-а не может эффективно превысить число partitions для одной consumer group: лишние threads будут простаивать.

@KafkaListener(topics = "orders.events", groupId = "billing")
void on(OrderPaid event, Acknowledgment ack) {
    billing.handle(event);
    ack.acknowledge();
}

Ack modes и offset

Ack mode определяет, когда Spring Kafka коммитит offsets: автоматически по batch/time/count или вручную через Acknowledgment.

Для at-least-once обычно обрабатывают сообщение, фиксируют бизнес-эффект, затем коммитят offset. Если упасть до commit, сообщение придёт повторно.

Retries, DLT и идемпотентность

DefaultErrorHandler может ретраить обработку с backoff, а после исчерпания попыток отправлять запись в DLT через DeadLetterPublishingRecoverer.

Отдельная ловушка — poison pill: запись, которую невозможно десериализовать (битый JSON, сменившаяся схема). Ошибка возникает ещё ДО вызова вашего listener-а, в десериализаторе, поэтому обычный error handler её не ловит, и контейнер встаёт в бесконечный цикл на одном offset. Лекарство — ErrorHandlingDeserializer: он оборачивает реальный десериализатор, ловит исключение и кладёт запись с пометкой, чтобы её можно было отправить в DLT, а не зацикливаться.

DLT не заменяет идемпотентность consumer-а. Любой retry/ре-баланс/падение между business commit и offset commit может дать повтор.

Transactions

Kafka transactions полезны, когда нужно атомарно consume-process-produce внутри Kafka. Для связки БД + Kafka чаще применяют transactional outbox, потому что одна транзакция на БД и Kafka сложно поддерживается надёжно.

Если listener пишет в БД, думайте о порядке: DB transaction, idempotency key и offset commit должны образовывать понятную гарантию.

Гарантии доставки

Семантика доставки определяется порядком «обработка ↔ commit offset». At-most-once: сначала commit, потом обработка — при падении сообщение теряется (зато без дублей). At-least-once: сначала обработка и бизнес-эффект, потом commit — при падении до commit сообщение придёт повторно (дубли возможны → нужна идемпотентность). Это разумный дефолт для большинства систем.

Exactly-once в пределах Kafka даёт транзакционный consume-process-produce (read_committed + транзакции продюсера). Но как только в цепочке появляется внешняя БД, «ровно один раз» обеспечивают не распределённой транзакцией, а идемпотентностью потребителя и/или transactional outbox.

Как отвечать на собеседовании

Свяжите producer (KafkaTemplate, key → партиционирование и порядок внутри партиции) и consumer (@KafkaListener + listener container, concurrency ≤ числа партиций в группе). Разберите три семантики доставки через момент commit offset (at-most/at-least/exactly-once) и почему at-least-once требует идемпотентности. Назовите ретраи + DLT (DefaultErrorHandler/DeadLetterPublishingRecoverer) и что DLT не заменяет идемпотентность; для БД+Kafka — transactional outbox, а не 2PC. Упомяните rebalancing при изменении состава группы.

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем