Демистификация семантики точно однократной обработки Kafka: Подробное руководство
Поймите семантику exactly-once в Kafka с помощью идемпотентных продюсеров, транзакций, потребителей read_committed и фиксации смещений.
Разбираемся в семантике Exactly-Once в Kafka: подробное руководство
Семантика exactly-once в Kafka может защитить конвейер потоковой обработки от дублирования выходных записей при повторных попытках продюсера, отказах брокеров или перезапуске приложения. Гарантия мощная, но она уже, чем кажется: Kafka может сделать транзакционными запись в Kafka и потребленные смещения. Она не может автоматически сделать exactly-once вашу внешнюю базу данных, платежный шлюз или HTTP API.
Используйте семантику exactly-once, когда дублирование выходных данных было бы дорогим или сложным для очистки, например, корректировки запасов, события баланса счета или производные топики состояния, потребляемые другими сервисами.
Гарантии доставки простыми словами
Приложения Kafka обычно говорят о трех моделях доставки.
- At-most-once: Ваше приложение может потерять записи, но не должно обрабатывать одну и ту же запись дважды. Это может произойти, когда смещения фиксируются до завершения обработки.
- At-least-once: Ваше приложение не должно терять записи, но может обработать запись более одного раза после повторной попытки или перезапуска.
- Exactly-once: Цикл чтение-обработка-запись в Kafka фиксирует выходные записи и потребленные смещения как одну транзакцию.
Последний пункт является ключевым. Семантика exactly-once наиболее сильна, когда приложение читает из Kafka, записывает результаты обратно в Kafka и фиксирует смещения в рамках одной транзакции.
Идемпотентные продюсеры
Идемпотентный продюсер предотвращает дублирование записей, вызванное повторными попытками продюсера. Kafka присваивает продюсеру ID и отслеживает порядковые номера для каждого продюсера и раздела. Если брокер уже принял пакет, а затем получает повторную попытку, он может отклонить дубликат вместо того, чтобы добавить его снова.
Для текущих клиентов Kafka идемпотентность включена по умолчанию, если вы не настраиваете конфликтующие настройки продюсера. Вы все еще можете установить ее явно:
enable.idempotence=true
acks=all
acks=all означает, что лидер ждет все синхронизированные реплики перед подтверждением записи. Идемпотентность также зависит от совместимых настроек повторных попыток и незавершенных запросов, поэтому избегайте переопределения настроек надежности продюсера, если вы не знаете эффекта в вашей версии клиента.
Идемпотентность защищает от повторных попыток продюсера, но не делает полный рабочий процесс обработки атомарным. Если ваше приложение потребляет из одного топика и производит в другой, вам нужны транзакции, чтобы связать выходные данные и фиксацию смещения вместе.
Транзакции Kafka
Транзакции позволяют одному продюсеру группировать несколько записей в атомарную единицу. Продюсеру нужен стабильный transactional.id.
transactional.id=inventory-adjuster-0
enable.idempotence=true
acks=all
Типичный поток транзакции:
- Инициализируйте транзакции при запуске приложения.
- Начните транзакцию.
- Потребите записи из входного топика.
- Произведите выходные записи.
- Отправьте потребленные смещения в транзакцию.
- Зафиксируйте транзакцию или прервите ее при сбое.
Если процесс аварийно завершается до фиксации, Kafka не показывает незафиксированные выходные данные потребителям read_committed. При перезапуске приложение может снова прочитать те же входные записи и произвести один зафиксированный результат.
Настройки потребителя, которые имеют значение
Потребители, читающие транзакционные выходные данные, должны использовать:
isolation.level=read_committed
enable.auto.commit=false
read_committed скрывает записи из прерванных транзакций. enable.auto.commit=false предотвращает фиксацию смещений потребителем вне транзакции.
Имя свойства имеет значение. Настройка потребителя Kafka — enable.auto.commit, а не auto.commit.enable.
Для ручного приложения потребитель-продюсер фиксация смещения должна быть частью транзакции продюсера. В Java-клиенте это означает использование API транзакционного продюсера, включая отправку смещений в транзакцию перед ее фиксацией.
Конкретный сценарий
Представьте топик orders и выходной топик inventory-events. Ваш сервис читает заказ, проверяет SKU и записывает событие списания запасов.
Без транзакций сбой после записи выходных данных, но до фиксации входного смещения может создать дублирующее списание после перезапуска. С транзакциями выходное событие и фиксация входного смещения либо успешны, либо терпят неудачу вместе. Перезапуск может повторно прочитать заказ, но только одно зафиксированное событие инвентаризации становится видимым для нижестоящих потребителей read_committed.
Ограничения, которые следует учитывать
Семантика exactly-once в Kafka не охватывает побочные эффекты вне Kafka, если вы не спроектируете их. Если тот же сервис также пишет в PostgreSQL или вызывает API биллинга, этот внешний побочный эффект требует собственного ключа идемпотентности, уникального ограничения, стратегии транзакций или шаблона outbox.
Транзакции также добавляют накладные расходы на координацию. Для простой загрузки логов, где дубликаты допустимы, идемпотентных продюсеров и потребителей at-least-once может быть достаточно.
Практический чек-лист
Используйте стабильный transactional.id для каждого экземпляра приложения или задачи. Не позволяйте двум активным продюсерам использовать один и тот же transactional.id одновременно.
Установите потребителей транзакционных выходных данных на read_committed. Отключите автоматическую фиксацию смещений в циклах транзакционной обработки.
Держите транзакции короткими. Большие транзакции могут увеличить задержку и замедлить восстановление.
Обрабатывайте внешние системы отдельно. Kafka может защитить состояние Kafka, но ваши записи в базу данных все еще требуют идемпотентного дизайна.
Полезный вывод: семантика exactly-once — это не волшебный переключатель. Это набор выборов продюсера, потребителя и транзакций, которые лучше всего работают для потоковой обработки Kafka-в-Kafka.