Как Kafka Consumer отслеживает, какие сообщения он уже прочитал? (Механизм offset’ов)

Ответ

Consumer в Kafka не "запоминает данные", а отслеживает свою позицию в логе партиции топика. Эта позиция называется смещением (offset).

Offset — это просто целое число, которое указывает на номер следующего сообщения, которое Consumer должен прочитать. Управление offset'ами — ключевая задача для обеспечения надежной доставки сообщений.

Где хранится offset?

По умолчанию, Consumer-группы хранят свои offset'ы в специальном внутреннем топике Kafka под названием __consumer_offsets.

Как происходит отслеживание (Commit)?

Процесс сохранения offset'а называется коммитом (commit). Существует две основные стратегии:

1. Автоматический коммит (Auto Commit)

  • Как работает: Consumer периодически (например, каждые 5 секунд) автоматически коммитит последний полученный offset.
  • Плюсы: Простота настройки (enable.auto.commit=true).
  • Минусы: Низкие гарантии доставки. Возможна как потеря сообщений (если приложение упало после обработки, но до авто-коммита), так и повторная обработка (если коммит произошел, а обработка не удалась).

2. Ручной коммит (Manual Commit)

  • Как работает: Разработчик полностью контролирует, когда коммитить offset. Обычно это делается после успешной обработки сообщения или пачки сообщений.
  • Плюсы: Высокая надежность и гибкость. Позволяет реализовать семантики at-least-once (как минимум один раз) и exactly-once (ровно один раз).
  • Минусы: Требует больше кода и внимания от разработчика.

Виды ручного коммита:

  • Синхронный (CommitSync): Блокирует выполнение, пока брокер не подтвердит коммит. Надежно, но медленнее.
  • Асинхронный (CommitAsync): Не блокирует выполнение. Быстрее, но нет гарантии, что коммит прошел успешно.

Пример логики ручного коммита (псевдокод):

// Бесконечный цикл чтения сообщений
for {
    // 1. Получаем сообщение от Kafka
    msg := consumer.Poll()

    // 2. Обрабатываем сообщение (например, сохраняем в базу данных)
    err := processMessage(msg)

    // 3. Если обработка прошла успешно, коммитим offset
    if err == nil {
        // Сообщаем Kafka, что это сообщение и все предыдущие успешно обработаны
        consumer.Commit(msg.Offset() + 1)
    } else {
        // Логируем ошибку и решаем, что делать дальше
        // (например, повторить попытку или отправить в Dead Letter Queue)
    }
}

Итог: Consumer отслеживает прогресс с помощью offset'ов, которые сохраняются (коммитятся) в Kafka. Ручной коммит является предпочтительным способом для построения надежных систем.