Ответ
Для гарантии доставки и предотвращения дублирования в Kafka используется механизм управления смещениями (offset management). Каждая группа потребителей (consumer group) отслеживает свою позицию (offset) в каждой партиции топика.
Существует три основных семантики доставки:
- At-most-once (не более одного раза): Смещение коммитится до обработки сообщения. При сбое сообщение будет потеряно.
- At-least-once (как минимум один раз): Смещение коммитится после обработки. При сбое до коммита сообщение может быть обработано повторно. Это самый частый сценарий.
- Exactly-once (строго один раз): Гарантирует, что сообщение обработано ровно один раз, даже при сбоях. Достигается с помощью транзакций и идемпотентного продюсера.
Способы управления смещениями:
Автоматический коммит (
enable.auto.commit=true
): Брокер периодически коммитит смещение за потребителя. Просто, но рискованно — возможны как потери, так и дубликаты.Ручной коммит (
enable.auto.commit=false
): Потребитель явно подтверждает обработку. Это надежный способ для реализации at-least-once.
Пример ручного коммита в Go с использованием sarama
(современный подход с ConsumerGroup
):
// ConsumerGroupHandler реализует логику обработки
type handler struct{}
func (h *handler) Setup(_ sarama.ConsumerGroupSession) error { return nil }
func (h *handler) Cleanup(_ sarama.ConsumerGroupSession) error { return nil }
func (h *handler) ConsumeClaim(sess sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
for msg := range claim.Messages() {
// 1. Обрабатываем сообщение
processMessage(msg)
// 2. Явно коммитим offset
sess.MarkMessage(msg, "")
}
return nil
}
Для строгой гарантии exactly-once необходимо использовать Transactional API в Kafka, что требует более сложной координации между продюсером и консьюмером.