Learning Platform
Глоссарий Troubleshooting
Урок 02.04 · 20 мин
Продвинутый
KRaftRaftMetadata LogLeader ElectionEpoch

KRaft контроллер: консенсус без ZooKeeper

NOTE

ZooKeeper использовался в Kafka до версии 3.3 (deprecated), полностью удалён в Kafka 4.0. Если вы встретите zookeeper.connect в документации или Stack Overflow — это устаревший подход. В Kafka 4.0 этого параметра не существует.

Kafka 4.0 управляет метаданными кластера через KRaft (Kafka Raft) — встроенный консенсусный протокол на основе Raft. KRaft — не внешний сервис, а часть самого Kafka.


Почему убрали ZooKeeper

ZooKeeper (Apache ZooKeeper) выполнял роль распределённого хранилища метаданных: список брокеров, назначение лидеров партиций, конфигурация топиков. Это создавало несколько проблем:

Внешняя зависимость. Отдельный кластер ZooKeeper нужно было развёртывать, мониторить и обслуживать независимо от Kafka. Два отдельных кластера вместо одного.

Ограничение масштаба. ZooKeeper плохо масштабировался при большом числе партиций. Кластер с 200 000+ партиций имел заметные задержки leader election и controller failover (от 30 секунд до нескольких минут).

Двойная модель согласованности. Kafka-брокеры и ZooKeeper имели разные модели транзакционности — это усложняло обработку граничных случаев при сбоях.

KRaft устраняет все эти проблемы: метаданные хранятся внутри самих Kafka-узлов, масштабирование до 1 000 000+ партиций, failover менее чем за 20 секунд (vs минуты с ZooKeeper).


Raft consensus: кворум большинства

KRaft реализует протокол Raft. Ключевая идея: кворум большинства (majority quorum).

Для N-узлового quorum необходимо (N/2)+1 узлов для принятия решения (округление вверх). Примеры:

Размер quorumНеобходимо для решенияТерпит отказов
110
321
532
743

Стандарт для production: 3 или 5 voter-узлов. Чётное число (2, 4, 6) не рекомендуется: при split-brain кластер 4/2 не может принять решение (ни одна сторона не имеет большинства из 3), тогда как при 2/2 ни одна сторона не имеет большинства из 3.

NOTE

Почему нечётное число важнее чётного? При 4 voter-узлах и одном сбое у вас остаётся 3 узла — кворум 3/4 работает. Но при втором сбое остаётся 2 узла — кворум 2/4 не работает (нужно 3). При 5 voter-узлах: 2 сбоя оставляют 3 узла — кворум 3/5 работает. Нечётные числа максимизируют отказоустойчивость для данного числа узлов.


Metadata log: event-sourced журнал состояния кластера

Metadata log — это append-only commit log (та же структура, что и пользовательские топики Kafka), в котором хранится вся история изменений состояния кластера.

Каждое изменение — новая запись в metadata log:

offset 0: RegisterBroker { nodeId: 1, host: "broker1", ... }
offset 1: RegisterBroker { nodeId: 2, host: "broker2", ... }
offset 2: CreateTopic { name: "orders", partitions: 6, rf: 3 }
offset 3: PartitionRecord { topic: "orders", partition: 0, leader: 1, isr: [1,2,3] }
...
offset 42: LeaderChange { topic: "orders", partition: 0, newLeader: 2 }

Это не реляционная таблица. Текущее состояние восстанавливается воспроизведением (replay) всего лога от начала. Для ускорения создаются снапшоты (snapshots) — сохранённое состояние на определённый offset. При запуске брокер загружает последний снапшот и воспроизводит только новые записи.

Инструмент для просмотра состояния KRaft quorum:

kafka-metadata-quorum.sh \
  --bootstrap-server localhost:9092 \
  describe --status

# Вывод:
# ClusterId:              abc123
# LeaderId:               1
# LeaderEpoch:            5
# HighWatermark:          1042
# MaxFollowerLag:         0
# MaxFollowerLagTimeMs:   12
# CurrentVoters:          [1,2,3]
# CurrentObservers:       []

KRaft leader election: последовательность событий

Когда контроллер-лидер недоступен (сбой, перегрузка, сетевая проблема), оставшиеся voter-узлы проводят новые выборы. Посмотрим на протокол выборов:

KRaft Leader Election: Raft Vote Protocol
Controller 1 (Candidate)
Controller 2 (Voter)
Controller 3 (Voter)
VoteRequest { term: 6, lastOffset: 1042 }VoteRequest { term: 6, lastOffset: 1042 }VoteResponse { granted: true }VoteResponse { granted: true }BeginEpoch { leaderId: 1, epoch: 6 }BeginEpoch { leaderId: 1, epoch: 6 }MetadataUpdate { brokers, topics, ... }MetadataUpdate { brokers, topics, ... }

Epoch fencing: защита от split-brain

Epoch — монотонно возрастающий счётчик, присваиваемый каждому новому лидеру контроллера. При каждых новых выборах epoch увеличивается на единицу.

Проблема split-brain: старый лидер (не знающий, что его заменили) продолжает рассылать команды брокерам. Без защиты — два контроллера одновременно управляют кластером, что приводит к несогласованности метаданных.

Epoch fencing решает это просто: каждая metadata-команда включает epoch отправителя. Брокер отклоняет любую команду с epoch, меньшим чем текущий известный epoch:

Старый лидер (epoch 5): LeaderChange { epoch: 5, ... }
Брокер (знает epoch 6): "Epoch 5 < 6 — отклонено. Команда устаревшего лидера."

Новый лидер (epoch 6): LeaderChange { epoch: 6, ... }
Брокер (знает epoch 6): "Epoch 6 = 6 — принято."

Epoch fencing гарантирует: в любой момент времени команды принимаются только от текущего легитимного лидера. Старый лидер автоматически становится безвредным.


KIP-853: динамическое членство в quorum

KIP-853 (Kafka 4.0) добавляет возможность изменять состав KRaft voter-узлов без остановки кластера.

До KIP-853: изменение quorum (добавление или замена controller-узла) требовало остановки кластера. Это делало операции по масштабированию control plane болезненными.

После KIP-853: новый voter-узел добавляется через kafka-metadata-quorum.sh:

# Добавить узел node.id=4 в quorum (без рестарта)
kafka-metadata-quorum.sh \
  --bootstrap-server localhost:9092 \
  add-controller \
  --controller-id 4 \
  --controller-directory-id <directory-id>

Новый узел сначала выступает как observer (получает metadata log, но не голосует), затем переходит в voter статус после достижения кворума существующих voter-узлов. Это полностью онлайн-операция.


Конфигурация KRaft в production

Минимальная конфигурация для dedicated controller-узла (Kafka 4.0):

# Controller-only node: не хранит пользовательские данные
process.roles=controller
node.id=1

# KRaft quorum: 3 voter-узла
controller.quorum.voters=1@controller1:9093,2@controller2:9093,3@controller3:9093

# Controller listener (не для клиентов)
listeners=CONTROLLER://0.0.0.0:9093
controller.listener.names=CONTROLLER
listener.security.protocol.map=CONTROLLER:PLAINTEXT

# Metadata log storage
log.dirs=/var/kafka/metadata

Ключевые выводы

  1. KRaft — встроенный Raft-протокол, заменивший ZooKeeper в Kafka 4.0. zookeeper.connect не существует.
  2. Metadata log — append-only event-sourced лог всех изменений кластера (не реляционная таблица).
  3. Raft quorum: нечётное число voter-узлов (3 или 5). Кворум = большинство.
  4. Epoch монотонно возрастает при каждых выборах. Epoch fencing отклоняет команды от устаревшего лидера.
  5. KIP-853: добавление/удаление controller-узлов без остановки кластера (Kafka 4.0+).
Проверка знанийKnowledge check
KRaft quorum состоит из 3 voter-узлов: controller-1, controller-2, controller-3. Controller-1 был лидером (epoch 4). Произошёл сетевой сбой: controller-1 изолирован, controller-2 и controller-3 провели выборы и избрали controller-2 новым лидером (epoch 5). Через 30 секунд сеть восстановилась. Controller-1 снова видит кластер и пытается отправить LeaderChange-команду с epoch 4. Что произойдёт?
ОтветAnswer
Epoch fencing отклонит команду. Брокеры уже знают epoch 5 от нового лидера (controller-2). Любая команда с epoch 4 (меньше текущего epoch 5) будет отклонена как устаревшая. Controller-1 сам обнаружит, что он больше не лидер (получит BeginEpoch с epoch 5 от controller-2), и перейдёт в follower-режим. Это ключевое свойство epoch fencing: старый лидер автоматически становится безвредным без ручного вмешательства.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. KRaft quorum настроен с 5 voter-узлами. Сколько одновременных сбоев voter-узлов может пережить кластер, сохраняя способность принимать metadata-команды?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 6