Apache Kafka – от ZooKeeper к KRaft

Оглавление

  1. Введение
  2. Kafka с ZooKeeper: классическая архитектура
  3. Kafka KRaft: новая архитектура без ZooKeeper
  4. Алгоритм Raft в Kafka (KRaft)
  5. Примеры конфигурации Kafka: ZooKeeper vs KRaft
  6. Docker Compose: примеры для ZooKeeper-кластера и KRaft-кластера
  7. Примеры клиентских подключений (Kafka Streams, Kafka Connect) для ZooKeeper и KRaft
  8. Kafka Connect
  9. Миграция кластера с ZooKeeper на KRaft
  10. Преимущества KRaft (Kafka без ZooKeeper)
  11. Сравнительная таблица: Kafka с ZooKeeper vs Kafka с KRaft
  12. Заключение

Введение

Apache Kafka изначально полагалась на ZooKeeper для хранения метаданных и координации работы брокеров. Однако начиная с версии 3.5 Kafka предлагает новый режим KRaft (Kafka Raft) – собственный встроенный консенсус на базе Raft, позволяющий полностью избавиться от ZooKeeper. В этой статье мы подробно сравним классическую архитектуру Kafka с ZooKeeper и новую архитектуру Kafka без ZooKeeper (KRaft). Рассмотрим роли ZooKeeper в старой архитектуре, устройство нового Quorum Controller в KRaft, принципы алгоритма Raft, приведем примеры конфигурации и кода, а также разберем процесс миграции существующего кластера с ZooKeeper на KRaft. В конце подведем итоги в виде сравнительной таблицы.

Примечание: ZooKeeper официально деприкейтнут в Kafka 3.5 и будет окончательно удален в Kafka 4.0. На практике это означает, что переход на KRaft – лишь вопрос времени, и уже сейчас технология считается готовой для промышленного использования (статус production ready с версии 3.3).

Kafka с ZooKeeper: классическая архитектура

Рис 1. Kafka с ZooKeeper

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

  • Регистрация брокеров и членство в кластере: При запуске каждый брокер Kafka подключается к ZooKeeper и создает временную znode-запись в определенном пути (например, /brokers/ids), сигнализируя о своем присутствии. Если брокер падает, соответствующая ephemeral-запись автоматически удаляется ZooKeeper, и это позволяет обнаружить выбытие узла.
  • Выбор контроллера кластера: ZooKeeper отвечал за выбор главного контроллера Kafka. Первый брокер, успевший создать znode /controller в ZooKeeper, становился контроллером (Controller) – центральным узлом, управляющим распределением метаданных. Если активный контроллер выходил из строя, ZooKeeper оповещал другие брокеры (через watcher), и следующий брокер, успевший заново создать /controller, становился новым контроллером. Таким образом ZooKeeper обеспечивал уникальность контроллера и автоматическое переизбрание.
  • Хранение метаданных топиков и конфигураций: Все сведения о топиках (список топиков, число партиций, репликации, лидеры партиций, список ISR и т.д.) сохранялись в узлах ZooKeeper (например, под путями /brokers/... и /topics/...). ZooKeeper хранил конфигурации топиков, глобальные параметры кластера, информацию о распределении реплик по брокерам. Также в ZooKeeper хранились списки ACL (списков доступа) и квоты клиентов Kafka.
  • Координация состояния брокеров: Контроллер Kafka при помощи ZooKeeper отслеживал состояние всех брокеров и партиций. Например, при изменениях (новый брокер подключился, топик создан или удалён, реплика стала недоступна и т.п.) контроллер получал уведомления от ZooKeeper (watcher-события). Контроллер, работая в старой архитектуре как единый поток (single-threaded loop), реагировал на эти события – считывал новые данные из ZooKeeper, обновлял свою копию метаданных и рассылал команды остальным брокерам. Так обеспечивалась согласованность представления кластера у всех брокеров (в конечном итоге).
  • Обновление состояния в реальном времени: Некоторые операции требовали координации через ZooKeeper. Примеры: когда лидер раздела (партиции) обновлял список ISR (in-sync replicas), он записывал изменения в ZooKeeper; при graceful shutdown брокер сообщал контроллеру, а контроллер через ZooKeeper обновлял метаданные (исключал брокер из ISR, перевыбирал лидеров на другие узлы).

Рис. 2 Producer отправляет сообщение в Kafka-кластер с ZooKeeper

Проблемы и ограничения подхода с ZooKeeper. Хотя такая схема работала, со временем проявились недостатки и узкие места:

  • Скалируемость и производительность: ZooKeeper изначально не рассчитан на очень большие нагрузки со стороны клиентов. В крупном Kafka-кластере контроллер генерирует большой поток операций чтения/записи ZooKeeper (так как вся метаинформация хранится в ZK и при каждом изменении требуется обновить ZK). Например, остановка брокера, хостящего тысячи партиций, приводит к тому, что контроллер должен последовательно обновить ZooKeeper для каждой партиции (удалить остановленный брокер из ISR, назначить новых лидеров и т.д.), что может занять значительное время. Аналогично, при падении контроллера новый контроллер должен прочитать из ZooKeeper весь снимок метаданных (все топики, партиции) – время этого процесса линейно растет с числом объектов в кластере. Таким образом, с ростом числа брокеров и партиций масштабируемость упирается в пропускную способность ZooKeeper. Многие операции (распространение обновлений метаданных, загрузка снимка при фейловере контроллера) выполняются за время, пропорциональное числу партиций, что на масштабах тысяч топиков ощутимо замедляет работу кластера и увеличивает время недоступности при сбоях.
  • Сложность инфраструктуры: Использование отдельного распределенного сервиса (ZooKeeper) увеличивает операционную сложность. Командам DevOps приходилось развертывать, настраивать и поддерживать кластер ZooKeeper отдельно от Kafka, с собственными параметрами конфигурации, мониторингом, механизмами безопасности и отказоустойчивости. По сути, поддерживались сразу две распределенные системы со своими нюансами. Любые проблемы в ZooKeeper (например, потеря кворума ZK) непосредственно влияли на доступность Kafka-кластера.
  • Ограничения ZooKeeper: ZooKeeper накладывал технические ограничения – максимальный размер данных узла (znode), ограничение на число watcher’ов, требования к кворуму. Например, для подтверждения любой операции требовалось большинство узлов ZooKeeper (кворум), и если кворума нет (например, сетевой раздел, падение большей части ZK-нод), то ZooKeeper перестает обслуживать запросы вообще. Это означало, что Kafka тоже фактически останавливалась, будучи не в состоянии ни читать, ни обновлять метаданные. Кроме того, ZooKeeper гарантирует линейную последовательность операций (сильную согласованность), но это достигается ценой производительности и требованием синхронной репликации в пределах ZK-кворума (протокол ZAB).
  • Консистентность метаданных и расхождения: В старой архитектуре Kafka возможны были временные рассинхронизации представления метаданных на разных брокерах. Контроллер после обновления ZooKeeper рассылал брокерам RPC-команды (UpdateMetadataRequest, LeaderAndISR и т.п.). Эти обновления приходили не мгновенно и не одновременно – брокеры применяли их с некоторой задержкой, и до того как все брокеры получили обновление, могли быть расхождения (например, клиент спросил у одного брокера метаданные топика, а тот еще не успел получить новую версию – тогда клиент мог получить устаревшую информацию о лидере раздела, что приводило к временным ошибкам при попытке отправки данных). В очень больших кластерах наблюдались сложные race-condition сценарии, когда некоторые брокеры могли не получить какую-то часть обновлений или применить их в другом порядке, что требовало дополнительных проверок и периодической сверки состояний.
  • Трудности сопровождения: На практике администрирование ZooKeeper вызывало сложности: обеспечение безопасности (ZK не изначально рассчитан на авторизацию клиентов, требуются дополнения), отладка проблем кворума, обновление версий ZooKeeper – все это добавляло трудозатрат. Любая дополнительная подсистема – это источник потенциальных ошибок конфигурации и эксплуатационных проблем. Упростить архитектуру – значит повысить надежность за счёт устранения лишних компонентов.

В результате, сообщество Kafka приняло решение отказаться от зависимости на ZooKeeper и реализовать собственный механизм консенсуса для хранения метаданных – именно так родился KRaft (Kafka Raft).

Архитектура Kafka до версии 3.x: ZooKeeper координирует кластер. Каждый брокер регистрируется в ZooKeeper; один из брокеров становится Controller, выбранный через ZooKeeper (эпhemeral node /controller). Контроллер хранит метаданные кластера в ZooKeeper (топики, партиции, лидеры, конфиги), и рассылает изменения другим брокерам по RPC. Данные в ZooKeeper реплицируются на все ZK-ноды.

Kafka KRaft: новая архитектура без ZooKeeper

KRaft (Kafka Raft) – это новый режим хранения метаданных Kafka, появившийся экспериментально в версии 2.8 и доведенный до production-ready статуса к версии 3.3. Начиная с Kafka 3.5 ZooKeeper режим объявлен устаревшим, и с Kafka 4.0 кластеры будут работать только в режиме KRaft. В режиме KRaft Kafka не использует ZooKeeper вообще – все метаданные хранятся и реплицируются средствами самой Kafka.

Рис. 4 Kafka KRaft

В основе KRaft – встроенная реализация алгоритма консенсуса Raft (отсюда и название: Kafka + Raft). Идея состоит в том, чтобы хранить метаданные кластера Kafka так же, как Kafka хранит обычные сообщения: в виде логов (журналов), реплицируемых на нескольких узлах. Вместо записи каждой изменения метаданных в ZooKeeper, контроллер Kafka добавляет запись в специальный журнал метаданных – по сути, внутренний топик __cluster_metadata (с одной партицией), поддерживаемый самими брокерами. Этот журнал реплицируется синхронно на группе узлов-контроллеров с помощью алгоритма Raft, что обеспечивает согласованность данных. Таким образом, ZooKeeper-ensemble заменяется на группу контроллеров Kafka, которые сами хранят и распространяют метаданные.

Quorum Controller и группа контроллеров. В режиме KRaft выделяется специальная роль – контроллер (controller) – в виде отдельного процесса Kafka, который участвует в кворуме контроллеров. Если раньше любой брокер мог стать контроллером (через ZooKeeper), то в KRaft мы явно конфигурируем, какие серверы являются контроллерами (т.е. участвуют в управлении метаданными). Контроллеры образуют небольшой кворум (обычно 3 узла для отказоустойчивости) и обмениваются записями журнала метаданных. В каждый момент один из них выступает как лидер контроллеров (Active Controller), аналогично ZooKeeper-контроллеру, но избранный теперь посредством протокола Raft, а не внешнего ZK. Остальные контроллеры – фолловеры (followers, или резервные контроллеры), которые реплицируют записи журнала и готовы принять лидерство в случае сбоя лидера.

Важно отметить, что контроллеры в KRaft – это сами процессы Kafka. Их можно запускать на тех же узлах, что и брокеры, либо на выделенных узлах. Kafka 3.5+ поддерживает три режима работы узла (параметр process.roles): broker (только брокер), controller (только контроллер) или broker,controller (комбинированный режим). В продакшен-кластере рекомендуется выносить контроллеры на отдельные узлы (для изоляции нагрузки), но в небольших и dev-средах допустимо комбинированное развёртывание. В любом случае, контроллеры – это Kafka-серверы, у которых включена роль controller и настроен участник кворума.

Хранение метаданных в журнале. При работе кластера с KRaft все изменения метаданных (создание или удаление топика, изменение конфигурации, присоединение нового брокера, ребаланс партиций и др.) фиксируются как события (записи) в журнале __cluster_metadata. Лидер-контроллер принимает такие изменения (например, через Admin API вызов или внутреннее событие), сериализует их в запись журнала и добавляет в локальный лог. Затем эта запись реплицируется на других контроллеров и считается коммитнутой, когда большинство контроллеров (кворум) записали её на диск. За счёт этого достигается устойчивое хранение: даже потеря одного контроллера (из 3) не приводит к потере метаданных – они уже есть у большинства. После коммита лидер-контроллер применяет изменение к своему снимку состояния (state machine) и рассылает обновления остальным компонентам.

Рис. 5 Producer отправляет сообщение в Kafka KRaft-кластер

Модель распространения метаданных: pull вместо push. Существенное отличие нового подхода – брокеры Kafka больше не получают напрямую команд от контроллера, как это было при ZooKeeper. Вместо этого введён механизм периодических запросов метаданных: брокеры сами запрашивают у контроллера обновления. Реализовано это через специальный API MetadataFetch – по сути, брокер хранит у себя смещение (offset) последнего применённого изменения метаданных и периодически (раз в несколько сотен миллисекунд) делает запрос лидеру-контроллеру: “дай мне записи журнала начиная с такого-то offset”. Контроллер отвечает батчем новых записей (если они есть), брокер применяет их и тем самым догоняет текущее состояние. Такой подход похож на обычный механизм fetch-запросов для данных (реплики подтягивают новые данные у лидера), только тут – для метаданных. Преимущество: контроль версий состояния теперь естественно обеспечивается последовательностью логов – каждый брокер знает до какого offset он обновлён. Если брокер отстал (например, был выключен), при восстановлении он запросит весь журнал с нужного места и быстро актуализирует своё состояние без необходимости полной перезагрузки из внешнего хранилища. Кроме того, контроллер больше не рассылает N отдельных RPC всем брокерам – обмен происходит по запросу, что масштабируется лучше (брокеры сами синхронизируются, контроллер отвечает на запросы, а не пушит обновления).

Рис. 6 Выбор нового лидера партиции при падении текущего лидера в KRaft

Структура коммуникаций в KRaft. Каждый контроллер и брокер взаимодействуют по определенным сетевым интерфейсам (листенерам). Обычно контроллеры используют отдельный слушатель, например CONTROLLER://<host>:9093, для внутрикластерного обмена Raft-сообщениями и MetadataFetch запросами. Брокеры обслуживают клиентов по обычным слушателям (PLAINTEXT/SSL на 9092 и т.д.). Брокер в режиме KRaft, помимо клиентского слушателя, обычно тоже имеет контроллер-листенер, по которому он подключается к активному контроллеру для fetch metadata. Таким образом, в конфигурации KRaft каждому узлу важно задать:

  • node.id – уникальный идентификатор (число) узла в кластере (вместо broker.id);
  • process.roles – набор ролей (например, broker,controller или одна из них);
  • controller.quorum.voters – список голосующих контроллеров в кворуме, в формате <nodeId>@<host>:<port> через запятую (например, 1@kafka1:9093,2@kafka2:9093,3@kafka3:9093);
  • listeners и controller.listener.names – настройка сетевых интерфейсов для брокера и контроллера. Например, listeners=PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093 и controller.listener.names=CONTROLLER означает, что узел слушает порт 9092 для клиентов, порт 9093 – для связи между контроллерами, и именно порт 9093 используется как контроллерный (обозначен именем CONTROLLER).

Метаданные, хранящиеся ранее в ZooKeeper – список брокеров, топики, партиции, конфигурации, ACL, информация о группах потребителей и др. – в KRaft хранятся в журнале. Конкретнее, журнал (топик __cluster_metadata) содержит записи различных типов: REGISTER_BROKER_RECORD (регистрация брокера), TOPIC_RECORD (создание топика), PARTITION_RECORD (параметры партиции и привязка к брокерам), CONFIG_RECORD (изменение конфигов) и т.д. – фактически событие для каждой изменения состояния кластера. Эти записи хранятся последовательно с постоянно растущим смещением (offset). Периодически Kafka делает снэпшоты (применяет все записи и сохраняет компактное состояние), чтобы журнал метаданных не рос бесконечно – тут применяется подход, аналогичный compact-топикам (сжатие старых записей).

Преимущества подхода KRaft:

  • Упрощение архитектуры: Удаление внешнего координационного сервиса (ZooKeeper) означает, что операторам нужно развертывать и поддерживать на одну систему меньше. Метаданные теперь управляются самой Kafka, и администраторы работают с едиными инструментами Kafka (конфигурация, мониторинг, ACL и др.), без отдельного зоопарка настроек ZooKeeper. Это снижает вероятность ошибок конфигурации и упрощает обновления (нет необходимости координированно обновлять ZK и Kafka).
  • Повышенная производительность и масштабируемость: За счет событийной природы распространения метаданных и использования кворумного журнала сняты многие узкие места ZooKeeper. Метаданные изменяются батчами записей в журнале, которые ассинхронно пишутся на диск и реплицируются – это дает прирост производительности за счет групповой обработки изменений. Распространение через репликацию лога предотвращает расхождение состояний – все брокеры применяют один и тот же упорядоченный поток событий, что гарантирует консистентность (пусть и eventual consistency с небольшим лагом между применением на разных узлах, аналогично обычной репликации данных). Масштабируемость кластера улучшается, так как нагрузка на хранение метаданных распределена между контроллерами (а не централизована в ZK), и при увеличении числа топиков/партиций Kafka справляется лучше. В частности, время восстановления после сбоя контроллера заметно сокращается – новый контроллер уже имеет локальную копию журнала метаданных и практически мгновенно становится актуальным (не нужно считывать тысячи записей из ZK). В эксперименте Confluent показано, что время восстановления контроллера в KRaft на порядок быстрее, чем с ZooKeeper, на больших кластерах.
  • Отказоустойчивость и доступность: KRaft использует кворум из N контроллеров, позволяя пережить выход из строя части из них. Для работы кластера требуется доступность только большинства контроллеров (например, при 3 контроллерах – кластер выстоит при падении 1). Это аналогично требованиям ZooKeeper, однако разница в том, что роль контроллера исполняют обычные узлы Kafka, которые можно развернуть более гибко. При сетевых разделениях, если часть контроллеров недоступна, но есть кворум – Kafka продолжит функционировать. Более того, даже если на короткое время нет контроллера (нет кворума), брокеры и клиенты могут временно продолжать работать с последней известной информацией, тогда как ZooKeeper в такой ситуации сразу блокировал все операции.
  • Снятие ограничений ZooKeeper: В Kafka KRaft больше нет ограничений на размер отдельных записей метаданных, числа вотчеров и т.п. Метаданные хранятся как записи Kafka, которые могут быть гораздо более объемными, если понадобится. Добавление новых полей и структур в метаданные проще, так как Kafka может эволюционировать формат журнала, не завися от схем ZooKeeper (например, поддержка новых фич через версионирование metadata.version).
  • Улучшенная консистентность представления: Хотя KRaft допускает временно неактуальное состояние брокера до следующего fetch-запроса, благодаря единому журналу исчезают ситуации перманентного расхождения метаданных. Каждый брокер применяет изменения строго в порядке общей упорядоченной последовательности (offset в журнале). Локальные кеши метаданных всегда могут быть проверены по смещению – если два брокера имеют offset X, то их представление идентично. При ZooKeeper приходилось полагаться на косвенные механизмы (версии ZK-нод и чендж-идентификаторы), и все же оставались случаи, когда брокер мог отставать.
  • Единый механизм для всей экосистемы: Переход на KRaft унифицирует работу компонентов Kafka. Например, Kafka Connect, Kafka Streams и другие клиенты всегда работали через bootstrap-брокеры (не напрямую с ZooKeeper). Теперь и сама Kafka-координация происходит через тот же кластер. Это упрощает поддержку – например, утилиты типа kafka-topics.sh или AdminClient больше не нуждаются в указании ZK-адреса для некоторых операций (раньше, для legacy-операций, требовалось подключение к ZK). Вся административная функциональность сосредоточена в Kafka API.

Конечно, новая архитектура – это большие изменения. Некоторые компромиссы и нюансы:

  • Требуется обновление версий Kafka-брокеров и клиентов до 3.x, поддерживающих KRaft (старые клиенты могут подключаться к KRaft-кластеру, поскольку протокол обмена данными не изменился, но инструменты администрирования старых версий, рассчитывавшие на ZooKeeper, работать не будут).
  • При переходе возможна сложность миграции (рассмотрим ниже).
  • На момент появления KRaft не все возможности были паритетны: например, режим JBOD (несколько log.dirs) в ранних версиях KRaft не поддерживался, однако к версии 3.8 эта функциональность была реализована. На Kafka 3.5–3.6 всё ещё накладывались некоторые ограничения в режиме KRaft (например, невозможность понизить версию протокола метаданных и откатиться обратно на ZooKeeper), но со временем эти пробелы закрываются.

Итак, KRaft приносит существенные улучшения и устраняет недостатки ZooKeeper-схемы. Далее рассмотрим подробнее внутренний алгоритм Raft, лежащий в основе KRaft, а затем практические аспекты настройки и миграции.

Алгоритм Raft в Kafka (KRaft)

Raft – это алгоритм распределенного консенсуса, обеспечивающий согласованное ведение журнала записей на нескольких узлах. В контексте Kafka KRaft, Raft используется группой контроллеров для согласованного хранения журнала метаданных. Рассмотрим основные понятия и механизмы алгоритма Raft применительно к Kafka:

  • Роли узлов: В любой момент каждый контроллер может находиться в одном из состояний – лидер (leader), кандидат (candidate) или последователь/избиратель (follower, иногда говорят voter – голосующий участник). В начальном состоянии после запуска все контроллеры начинают как follower’ы. Лидер – это активный контроллер, который принимает изменения и пишет их в журнал. Последователи – реплицируют записи из журнала лидера. Если лидер выходит из строя или не доступен, начинается процесс выбора нового лидера из числа остальных контроллеров. Есть также понятие наблюдателя (observer) – контроллер, который реплицирует журнал пассивно, но не участвует в голосованиях. Однако в Kafka все контроллеры quorum обычно являются голосующими (наблюдателей может и не быть, это опция для расширения без увеличения кворума).
  • Термы (terms) и выборы: Raft разбивает время на электоральные термы (пронумерованные эпохи). Каждый раз, когда требуется переизбрать лидера, начинается новый term с инкрементом номера. У каждого контроллера хранится текущий известный term. Если в течение заданного таймаута контроллер-follower не получает никаких сообщений от лидера (heartbeat или новых записей), он предполагает отсутствие лидера и переходит в состояние candidate, увеличивая свой текущий term на 1.
  • Запрос голосов: Кандидат рассылает всем известным контроллерам запрос на голосование (RequestVote), содержащий номер текущего term и индекс/смещение последней записи в своем журнале. Каждый получатель (follower) проверяет условия перед голосованием:
    1. Term кандидата должен быть не меньше собственного текущего term (чтобы не голосовать за устаревшего кандидата). Если у кандидата term меньше, голос не дается.
    2. Получатель ещё не голосовал в этом term (каждый узел может отдать голос только одному кандидату за term).
    3. Журнал кандидата не отстает от журнала голосующего узла – т.е. последняя запись кандидата не старше, чем последняя запись у голосующего. Это ключевое отличие Raft: узел отдаст голос только тому кандидату, чей журнал актуален как минимум не меньше его собственного. Благодаря этому выбирается кандидат с наиболее свежими записями.
    Если все условия выполнены, контроллер-фолловер помечает, что проголосовал за данного кандидата, и отвечает согласием (vote granted). Голоса фиксируются (Raft хранит информацию, кому уже отдал голос, чтобы не передумать после перезапуска).
  • Победа кандидата: Как только кандидат набирает большинство голосов (кворум > N/2), он становится новым лидером текущего term. С этого момента кандидат превращается в лидер и начинает выполнять обязанности лидера – принимать новые записи в журнал и рассылать (реплицировать) их остальным. Все остальные узлы, узнав о новом лидере (из голосования или получив сообщение от него), переходят в followers. Таким образом, Raft гарантирует, что в рамках одного term может быть избран не более одного лидера (благодаря системе голосования и условиям). Если ни один кандидат не набрал большинство (например, голоса распались поровну), через случайный короткий таймаут кандидаты увеличат term и повторят выборы – рандомизация задержки предотвращает вечной конфликт (gridlock).
  • Принцип безопасности лидера: Благодаря правилу голосования “только актуальный журнал”, Raft гарантирует, что новый лидер содержит все зафиксированные записи предыдущих терминов. Иначе говоря, лидер всегда обладает самым полным знанием состояния. Это свойство важно для консистентности: если запись была подтверждена большинством при старом лидере, хотя бы один узел из большинства проголосует только за кандидата, у которого эта запись уже есть в журнале, тем самым она не потеряется при переходе лидерства.
  • Репликация журнала (Raft Log Replication): Лидер принимает команды на изменение состояния (в Kafka KRaft – это операции изменения метаданных) и добавляет соответствующие записи в свой локальный лог с новым индексом (offset). Далее лидер рассылает AppendEntries сообщения (в Kafka реализация – follower’ы сами запрашивают у лидера данные, об этом ниже) всем follower’ам с инструкцией добавить эти записи в их журналы. Когда запись достигает большинства узлов (лидер + достаточно follower’ов), она считается коммитнутой. Лидер отмечает ее как commit и сообщает об успехе (например, подтверждает выполнение операции, сигнализируя вызывающему компоненту). Если лидер терпит сбой до того, как запись попала на большинство, запись останется некоммитнутой и может быть отброшена новым лидером (чтобы не было “раздвоения истории”). Таким образом достигается консенсус – только записи, подтвержденные кворумом, станут видимы и применены на всех узлах.
  • Модель в Kafka KRaft – push vs pull: Классический Raft описывает активного лидера, пушащего записи follower’ам (AppendEntries RPC). В Kafka KRaft реализована модель, более свойственная Kafka: follower’ы сами запрашивают у лидера новые записи (pull-модель). Фактически, контроллеры-фолловеры, подобно брокерам-репликам, шлют лидеру-контроллеру fetch-запрос с указанием, до какого offset у них есть записи, и лидер отвечает следующими записями. Это архитектурное решение унифицировано с существующим механизмом репликации Kafka и упрощает реализацию (можно использовать тот же код управления логами, механизм сжатия, контроля объема, что и для обычных топиков). С точки зрения алгоритма консенсуса это не нарушает его свойств – важен факт, что лидер дожидается подтверждений от большинства (в данном случае подтверждением служит продвижение high-water mark у follower’ов).
  • Доступность и отказоустойчивость: В типичной конфигурации KRaft с 3 контроллерами кластер выдерживает отказ одного контроллера (останется 2 из 3, кворум есть). Если падают 2 из 3 – остаётся меньше половины, кворума нет, контроллер не может принимать новые изменения (метаданные временно неизменяемы), ноKafka-кластер продолжит обслуживать клиентов с существующими метаданными, пока не вернется кворум (новые темы не создашь, но отправка/чтение по старым – продолжится, так как брокеры и клиенты могут работать с кешированными данными). Как только хотя бы один контроллер из упавших восстановится – кворум снова соберется и автоматически из оставшихся/восстановленных выберется лидер, продолжив с консистентного места журнала. Таким образом, Raft-кворум аналогичен ZooKeeper по требованиям (нужны >=2f+1 узлов для отказоустойчивости f отказов), но выигрывает за счет интеграции с самой Kafka и быстрого восстановления. При возвращении большинства контроллеров время перерыва минимально, потому что новый лидер уже синхронизирован.

Последовательность процесса Raft – пример выборов лидера:

  1. Все 3 контроллера запущены. Начальный term = 0, лидера нет.
  2. По истечении таймаута один из контроллеров (пусть ID=2) переходит в кандидаты (term=1) и рассылает запросы голоса остальным.
  3. Остальные контроллеры (ID=1 и ID=3) получают запрос. Они ещё не голосовали в term=1 и их журналы не содержат данных или не дальше, чем у кандидата, поэтому отдают свои голоса кандидату 2.
  4. Контроллер 2 получает 2 голоса из 3 (включая свой собственный голос, итого 3 из 3) – большинство достигнуто. Он становится лидером term=1 (Active Controller).
  5. Контроллеры 1 и 3 переходят в состояние follower и начинают получать сообщения от лидера 2.
  6. Когда требуется изменить метаданные (например, создать топик), лидер 2 записывает событие в журнал (новая запись с incrementing offset) и уведомляет фолловеров. Фолловеры запрашивают запись, получают и добавляют ее в свой журнал, затем подтверждают. Получив подтверждения от обоих, лидер фиксирует запись как commit (достигнут кворум 3 из 3) и применяет изменение (топик считается созданным).
  7. Если лидер 2 выйдет из строя, фолловеры в течение таймаута не получат от него heartbeat. Начнется новый терм (term=2): один из них станет кандидатом и пройдёт аналогичная процедура голосования для выбора нового лидера.

Рис. 7 Выборы лидера и репликация по алгоритму Raft

Диаграммы состояний и обмена сообщениями Raft в контексте Kafka можно изобразить визуально, однако текстовый формат ограничен. Главное – Raft обеспечивает, что никогда не будет двух лидеров одновременно (при условии честного большинства), и что все коммитнутые записи метаданных не потеряются при смене лидера. Это и позволяет Kafka KRaft безопасно отказаться от ZooKeeper: журнал метаданных становится единственным источником правды (single source of truth), как ранее ZooKeeper, но с более высокой производительностью и лучшей интеграцией в сам Kafka.

Примеры конфигурации Kafka: ZooKeeper vs KRaft

Настройка Kafka-кластера в режиме ZooKeeper и в режиме KRaft существенно различается. Рассмотрим ключевые моменты конфигурации на примере файлов server.properties и необходимых утилит.

Конфигурация Kafka с ZooKeeper (до 3.x)

В классическом режиме указывается адрес ZooKeeper-кластера и брокер получает ID либо динамически через ZK, либо статически. Пример минимальной конфигурации brokerа:

# server.properties для Kafka с ZooKeeper
broker.id=1                             # идентификатор брокера (уникален в кластере)
zookeeper.connect=zk1:2181,zk2:2181/kafka-cluster   # адрес(а) ZooKeeper (можно несколько через запятую, + опциональный chroot префикс)
listeners=PLAINTEXT://0.0.0.0:9092      # на каких портах/интерфейсах слушать соединения клиентов Kafka
log.dirs=/var/lib/kafka/data           # директория для логов партиций (топиков)
num.network.threads=3
num.io.threads=8
socket.send.buffer.bytes=102400
# ... другие параметры (аналогичные дефолтам, опущены)

Здесь главное – наличие zookeeper.connect. Kafka-брокер при старте подключится к ZooKeeper, создаст там записи и будет ожидать команд от контроллера (который тоже определяется через ZooKeeper). В остальном конфигурация стандартна. Если кластер ZooKeeper защищен SASL или TLS – настраиваются доп. параметры ZK (например, zookeeper.clientCnxnSocket и др.), но в простом случае достаточно указать хост:порт.

Конфигурация Kafka в режиме KRaft (Kafka 3.5+, без ZooKeeper)

В KRaft нет zookeeper.connect. Вместо этого указываются роли узла и параметры кворума. Пример конфигурации комбинированного узла (и брокер, и контроллер одновременно) для первого узла кластера:

# server.properties для Kafka KRaft (комбинированный узел)
node.id=1
process.roles=broker,controller                     # узел выполняет обе роли
controller.quorum.voters=1@kafka1:9093,2@kafka2:9093,3@kafka3:9093   # список контроллеров-кворума
controller.listener.names=CONTROLLER                # имя listener'а, используемого для обмена между контроллерами
listeners=PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093   # слушать 9092 для клиентов, 9093 для контроллеров
advertised.listeners=PLAINTEXT://kafka1:9092        # как этот брокер будет виден клиентам (по hostname)
log.dirs=/var/lib/kafka/data                        # директория логов (в т.ч. для журнала метаданных контроллера)
# Параметры репликации внутренних топиков (например, для Kafka Connect, Consumer Offsets):
offsets.topic.replication.factor=3
transaction.state.log.replication.factor=3
# ... прочие настройки

В конфигурации каждого узла должны быть: уникальный node.id (целое число, можно нумеровать с 1 или с 0 по желанию, но он не должен совпадать с никаким старым broker.id, если вы мигрируете – ID-контроллеры и брокеры делят одно пространство имен), одинаковый controller.quorum.voters (список всех узлов-контроллеров кластера). Заметим формат voters: каждая запись – это nodeId@host:port. Здесь port – порт контроллер-листенера на узле. Мы для простоты используем контроллер-порт 9093 на каждом узле.

Если контроллеры вынесены на отдельные узлы (dedicated controllers), тогда на контроллерских узлах process.roles=controller, а listeners обычно содержит только контроллерный интерфейс (например, CONTROLLER://0.0.0.0:9093). На брокерских узлахprocess.roles=broker и им тоже нужен знать список контроллеров (controller.quorum.voters) и настроить, как они будут подключаться к контроллерам. В этом случае брокеры не слушают порт CONTROLLER, а только используют его для исходящих подключений к контроллерам. В комбинированном режиме, как в примере выше, каждый узел и брокер, и контроллер одновременно.

Инициализация кластера KRaft (cluster ID): Перед первым запуском кластера в KRaft-режиме необходимо сгенерировать уникальный идентификатор кластера и проставить его в метаданных хранилища. Делается это с помощью утилиты kafka-storage.sh. Шаги:

  1. Генерация UUID для кластера: $ KAFKA_CLUSTER_ID=$(./bin/kafka-storage.sh random-uuid) Эта команда выводит случайный UUID, например: XyZ123AbcDeFgHiJkLmNoPq. Его нужно сохранить – он должен быть одинаковым для всех узлов кластера.
  2. Форматирование логовых директорий на каждом узле с указанием cluster ID: $ ./bin/kafka-storage.sh format -t $KAFKA_CLUSTER_ID -c config/server.properties Опция -t задает cluster ID, -c указывает на конфиг-файл данного узла. Эта команда инициирует структуру данных в log.dirs, в частности создаёт файл meta.properties, где сохраняет cluster ID и node.id узла. Важно: cluster ID должен быть один и тот же на всех узлах (поэтому мы генерируем один UUID и используем его повсюду), а node.id у каждого – свой. Если случайно запустить два узла с одинаковым node.id или с разными cluster ID – кластер не соберется (каждый узел проверяет соответствие cluster ID при подключении).

После форматирования, можно запускать Kafka-брокеры. Они прочитают из meta.properties свой cluster ID и node.id и начнут обмениваться сообщениями Raft. Один из контроллеров станет лидером и инициализирует пустой журнал метаданных. Кластер готов к работе.

Пример meta.properties: (создается автоматически утилитой форматирования)

node.id=1
version=1
cluster.id=XyZ123AbcDeFgHiJkLmNoPq

Для каждой комбинации broker/controller конфигов будет свой файл meta.properties в соответствующем log.dir.

Сравнение настроек ZooKeeper vs KRaft: Как видим, основные отличия:

  • Отсутствует строка zookeeper.connect в KRaft-режиме. Вместо неё – параметры кворума контроллеров.
  • Вместо broker.id используется node.id (Kafka больше не выдает broker.id автоматически, нужно прописать).
  • Добавляется параметр process.roles. В ZK-режиме такого нет – все узлы автоматически и брокеры, и при случае могут быть контроллером (через ZooKeeper).
  • Появляются параметры controller.quorum.voters и controller.listener.names + отдельный listener для контроллеров.
  • Параметр listener.security.protocol.map в случае нестандартных названий listeners (например, CONTROLLER) нужно настроить, по умолчанию Kafka считает незнакомое имя протокола PLAINTEXT. В нашем примере можно явно добавить listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT в конфиг (это особенно нужно, если используете SSL между контроллерами).
  • Рекомендуется увеличить факторы репликации для внутренних топиков (offsets, транзакции) до числа брокеров, раз ZooKeeper нет – но это и раньше делали на продакшене.

Docker Compose: примеры для ZooKeeper-кластера и KRaft-кластера

Для наглядности рассмотрим запуск локального кластера Kafka двумя способами: (1) с ZooKeeper, (2) с KRaft. В каждом примере используем Docker Compose для развёртывания необходимых контейнеров.

Примечание: Эти Compose-файлы предназначены для локальной разработки и тестирования. Они не нацелены на production (где нужен устойчивый storage, настройка безопасности и т.д.), а служат иллюстрацией различных конфигураций. Будем использовать образы Bitnami Kafka, которые позволяют переключаться в KRaft-режим через переменные окружения.

Docker Compose: кластер Kafka + ZooKeeper

В состав входят: один контейнер ZooKeeper и несколько (например, 3) контейнеров Kafka-брокеров. Конфигурация брокеров через переменные окружения включает адрес ZooKeeper и уникальный broker.id. Ниже приведен фрагмент docker-compose.yml:

services:
  zookeeper:
    image: bitnami/zookeeper:3.8
    container_name: zookeeper
    environment:
      - ALLOW_ANONYMOUS_LOGIN=yes       # позволим без аутентификации для простоты
    ports:
      - "2181:2181"

  kafka1:
    image: bitnami/kafka:3.5.0
    container_name: kafka1
    depends_on:
      - zookeeper
    environment:
      - KAFKA_CFG_BROKER_ID=1
      - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka1:9092
      - KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR=1       # для локал. кластера RF=1
      - ALLOW_PLAINTEXT_LISTENER=yes    # Bitnami специф.: разрешить PLAINTEXT без SASL
    ports:
      - "9092:9092"
    networks:
      - kafka-net

  kafka2:
    image: bitnami/kafka:3.5.0
    container_name: kafka2
    depends_on:
      - zookeeper
    environment:
      - KAFKA_CFG_BROKER_ID=2
      - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka2:9092
      - KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR=1
      - ALLOW_PLAINTEXT_LISTENER=yes
    networks:
      - kafka-net

  kafka3:
    image: bitnami/kafka:3.5.0
    container_name: kafka3
    depends_on:
      - zookeeper
    environment:
      - KAFKA_CFG_BROKER_ID=3
      - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka3:9092
      - KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR=1
      - ALLOW_PLAINTEXT_LISTENER=yes
    networks:
      - kafka-net

networks:
  kafka-net:
    driver: bridge

Здесь три брокера (kafka1, kafka2, kafka3) подключаются к zookeeper:2181. Переменные KAFKA_CFG_* настраивают соответствующие параметры server.properties. Например, KAFKA_CFG_ZOOKEEPER_CONNECT задает zookeeper.connect. Bitnami-образ при старте автоматически сформирует конфиг на основе этих переменных. ALLOW_PLAINTEXT_LISTENER=yes снимает ограничения и позволяет не настраивать SASL/SSL для теста. В реальном окружении, конечно, связь должна быть защищенной.

С таким Compose-файлом мы получим стандартный Kafka-кластер: ZooKeeper на порту 2181, брокеры на 9092 (через хостнеймы kafka1, kafka2, kafka3 в docker-сети). Клиентам можно подключаться, например, к localhost:9092 (к kafka1, который опубликовал advertised listener как kafka1:9092 – внутри сети docker, но если на хост-машине нет такого dns, удобнее заменить advertised.listeners на localhost:9092 для одиночного узла или настроить mapping).

Docker Compose: кластер Kafka KRaft (без ZooKeeper)

Теперь Compose-файл для кластера Kafka 3.5+ в режиме KRaft. Уберем сервис ZooKeeper и зададим переменные для KRaft: KAFKA_CFG_NODE_ID, KAFKA_CFG_PROCESS_ROLES, KAFKA_CFG_CONTROLLER_QUORUM_VOTERS, а также единый KAFKA_KRAFT_CLUSTER_ID для всех узлов (чтобы все использовали один cluster ID). Мы можем заранее сгенерировать UUID (например, с помощью uuidgen) либо воспользоваться Bitnami-образом: в нем, если не указать KAFKA_KRAFT_CLUSTER_ID, он сам создаст, но каждый узел сделает свой – нам так не подходит. Поэтому лучше явно задать. Допустим, cluster ID = a123KafkaClusterID. Compose:

services:
  kafka1:
    image: bitnami/kafka:3.5.0
    container_name: kafka1
    environment:
      - KAFKA_CFG_NODE_ID=1
      - KAFKA_CFG_PROCESS_ROLES=broker,controller
      - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@kafka1:19093,2@kafka2:19093,3@kafka3:19093
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:19093
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka1:9092
      - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
      - KAFKA_KRAFT_CLUSTER_ID=a123KafkaClusterID
      - ALLOW_PLAINTEXT_LISTENER=yes
    ports:
      - "9092:9092"
    networks:
      - kafka-net

  kafka2:
    image: bitnami/kafka:3.5.0
    container_name: kafka2
    environment:
      - KAFKA_CFG_NODE_ID=2
      - KAFKA_CFG_PROCESS_ROLES=broker,controller
      - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@kafka1:19093,2@kafka2:19093,3@kafka3:19093
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:19093
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka2:9092
      - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
      - KAFKA_KRAFT_CLUSTER_ID=a123KafkaClusterID
      - ALLOW_PLAINTEXT_LISTENER=yes
    networks:
      - kafka-net

  kafka3:
    image: bitnami/kafka:3.5.0
    container_name: kafka3
    environment:
      - KAFKA_CFG_NODE_ID=3
      - KAFKA_CFG_PROCESS_ROLES=broker,controller
      - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@kafka1:19093,2@kafka2:19093,3@kafka3:19093
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:19093
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka3:9092
      - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
      - KAFKA_KRAFT_CLUSTER_ID=a123KafkaClusterID
      - ALLOW_PLAINTEXT_LISTENER=yes
    networks:
      - kafka-net

networks:
  kafka-net:
    driver: bridge

Здесь:

  • Используем контроллер-листенер на порту 19093 (чтобы не конфликтовал с клиентским портом 9092; можно 9093 тоже, но выбрали так для явности).
  • controller.quorum.voters прописан одинаково для всех – важно, hostnames должны совпадать с advertised_listeners и тем, как контейнеры видят друг друга. Мы называем сервисы kafka1, kafka2, kafka3 – эти имена станут DNS в сети kafka-net, поэтому их и используем.
  • KAFKA_KRAFT_CLUSTER_ID задаем одной строкой (короче 22 символа Base64, Bitnami принимает строку, но судя по issue, он ее может слегка перегенерировать – но для простоты предположим, что примет как есть). Если вдруг Bitnami игнорирует и генерит свой (как заметили в GitHub issue, у них был баг с последним символом), то возможно cluster ID чуть отличится, но критично, чтобы у всех узлов он был одинаковый.
  • ALLOW_PLAINTEXT_LISTENER=yes опять же для возможности не заморачиваться с сертификатами в тесте.

В реальном мире можно было бы вместо трех брокеров-комбинированных, сделать 3 контроллера (process.roles=controller) + скажем 2-3 чистых брокера (process.roles=broker). Для Compose можно добавить, например, kafka4, kafka5 без controller роли, и controller.quorum.voters у них все равно указывает на kafka1..kafka3. Тогда kafka4,5 будут хранить только данные топиков, а контроллерами выступят kafka1-3. Но для простоты примера мы сделали 3 узла выполняющими обе функции.

Запустив этот Compose, мы получим Kafka-кластер из 3 узлов без ZooKeeper. Они при первом старте автоматически сформатируют свои хранилища (Bitnami image под капотом делает kafka-storage.sh format при наличии KAFKA_KRAFT_CLUSTER_ID). Первый поднявшийся контроллер станет лидером и создаст пустой журнал метаданных. Проверить работу можно, например, создав топик через Admin Client и убедившись, что он создается без участия ZooKeeper.

Подключение клиентов: Для клиентов Kafka (продюсеры, консюмеры, Streams) способ подключения практически не отличается между режимами. Им, как и прежде, нужен только список bootstrap.servers – адреса брокеров. В ZooKeeper-режиме клиенты тоже обычно не знали о ZK (исключение – старые консумеры до Kafka 0.10, но их уже нет). Таким образом, код клиента Kafka не меняется при переходе кластера на KRaft. Ниже приведем примеры кода на Java и Go для продюсера/консюмер и Kafka Streams, демонстрирующие подключение к кластеру.

Примеры клиентских подключений (Kafka Streams, Kafka Connect) для ZooKeeper и KRaft

Как отмечено, для приложений, использующих Kafka (продюсеры, консюмеры, Streams API, Kafka Connect), архитектурные изменения “под капотом” кластера максимально прозрачны. Клиентам не нужен ZooKeeper – они взаимодействуют только с Kafka-брокерами. Тем не менее, приведем несколько примеров – как на Java, так и на Go – показывающих, что при различных режимах кластера код остается одинаковым, меняется лишь адрес bootstrap-сервера (и версия библиотек, чтобы они поддерживали новейшие фичи Kafka).

Пример: Kafka Streams на Java

Kafka Streams – это клиентская библиотека (Java) для потоковой обработки, встроенная в Kafka. Она не требует отдельного кластера – Streams-приложение само является клиентом, потребляющим и записывающим данные в топики. Для Streams не важно, управляется ли кластер ZooKeeper-ом или KRaft – достаточно указать bootstrap.servers и идентификатор приложения. Ниже простой пример Streams-приложения, которое читает сообщения из входного топика, переводит текст в верхний регистр и пишет в выходной топик:

Properties props = new Properties();
props.put(StreamsConfig.APPLICATION_ID_CONFIG, "my-streams-app");         // имя Streams приложения (уникально в кластере)
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka1:9092");        // адрес одного или нескольких bootstrap брокеров
props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass().getName());
props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass().getName());

// Определяем топологию обработки
StreamsBuilder builder = new StreamsBuilder();
KStream<String, String> inputStream = builder.stream("input-topic");
KStream<String, String> upperStream = inputStream.mapValues(value -> value.toUpperCase());
upperStream.to("output-topic");

// Создаем и запускаем Streams
KafkaStreams streams = new KafkaStreams(builder.build(), props);
streams.start();

В этом коде bootstrap.servers может указывать на кластер как с ZooKeeper (например, broker1:9092) так и KRaft – в обоих случаях Streams подключится к брокерам и будет работать. Разницы в коде нет. Важное уточнение: если кластер используется без ZooKeeper, необходимо чтобы версия библиотек Kafka Streams была не ниже версии кластера. Т.е. для Kafka 3.5 без ZooKeeper желательно использовать kafka-streams версии 3.5 (или совместимую). Но в целом Streams API изменений не потребовала для работы с KRaft – она изначально не работала с ZK напрямую.

Пример: Kafka Producer и Consumer на Go

Для взаимодействия с Kafka из Go-экосистемы популярны несколько библиотек, например Sarama (Shopify) или официальная обертка confluent-kafka-go на базе C-библиотеки librdkafka, а также Franz-go (segmentio/kafka-go). Все они работают через сетевое API Kafka. Покажем упрощенный пример с использованием библиотеки segmentio/kafka-go (для наглядности, она имеет довольно простой интерфейс). Пример включает отправку сообщения и получение его:

import (
    "context"
    "github.com/segmentio/kafka-go"
)

// Настройка писателя (producer) для топика "demo-topic"
w := kafka.NewWriter(kafka.WriterConfig{
    Brokers: []string{"localhost:9092"},  // адрес bootstrap брокера
    Topic:   "demo-topic",
})
// Отправка сообщения
err := w.WriteMessages(context.Background(),
    kafka.Message{
        Key:   []byte("myKey"),
        Value: []byte("Привет, Kafka!"),
    },
)
if err != nil {
    log.Fatal("Не удалось отправить сообщение:", err)
}
w.Close()

// Настройка читателя (consumer) для того же топика
r := kafka.NewReader(kafka.ReaderConfig{
    Brokers:   []string{"localhost:9092"},
    GroupID:   "demo-group",
    Topic:     "demo-topic",
})
msg, err := r.ReadMessage(context.Background())
if err != nil {
    log.Fatal("Ошибка чтения:", err)
}
fmt.Printf("Получено сообщение: ключ = %s, значение = %s\n", string(msg.Key), string(msg.Value))
r.Close()

В этом Go-коде Brokers: []string{"localhost:9092"} – это по сути аналог bootstrap.servers. Если наш Kafka кластер ZooKeeper-режима – мы указываем адрес любого брокера. Если KRaft – то же самое, адрес брокера. Ничего про ZooKeeper клиент не знает. Важно убедиться, что библиотека совместима с версией Kafka: например, очень старые версии клиентов могут некорректно работать, если Kafka 4.0 отключит какие-то старые API, но большинство популярных клиентов поддерживают протоколы вплоть до последних версий (Kafka протокол обратносовместим).

Kafka Connect

Kafka Connect – это отдельный фреймворк для коннекторов, который обычно запускается как кластер коннект-воркеров. Connect никогда не использовал прямого подключения к ZooKeeper (начиная с Kafka 0.10, когда Connect был введен). Он хранит свои рабочие метаданные (офсеты коннекторов, конфигурации) в специальных внутренних топиках Kafka. Поэтому для Connect при работе с KRaft практически ничего не меняется.

Чтобы запустить Connect-воркер, мы указываем bootstrap-серверы Kafka, а не ZooKeeper. Например, в файле connect-distributed.properties (или переменных окружения) будут строки:

bootstrap.servers=kafka1:9092,kafka2:9092   # адреса брокеров Kafka (ZooKeeper здесь не нужен)
group.id=connect-cluster                   # групповой идентификатор для Connect-кластера
offset.storage.topic=_connect-offsets      # внутренний топик для офсетов
config.storage.topic=_connect-configs      # внутренний топик для конфигов коннекторов
status.storage.topic=_connect-status       # внутренний топик для статусов
offset.storage.replication.factor=3        # фактор репликации этих топиков
config.storage.replication.factor=3
status.storage.replication.factor=3

Обратив внимание, нигде нет zookeeper.connect. Connect-воркеры координируются через потребительские группы Kafka (они по сути являются потребителями этих внутренних топиков). Поэтому, независимо от того ZooKeeper или KRaft под капотом у Kafka-брокеров, Connect взаимодействует только с брокерами.

Kafka Streams и KRaft: Единственный момент – утилита kafka-streams-application-reset.sh (сброс состояния Streams-приложения) в старых версиях Kafka могла требовать доступ к ZooKeeper для сброса состояния consumer group. Но уже давно она работает через брокеры. В Kafka 3.x все клиентские утилиты используют AdminClient. Таким образом, для разработчиков приложений на Kafka Streams/Connect переход на KRaft не требует изменений кода – достаточно обновить версию клиентов при необходимости.

Подведем итог: клиенты Kafka не взаимодействуют с ZooKeeper, поэтому для них разницы практически нет, что кластер под управлением ZooKeeper, что под управлением KRaft. Главное – Kafka должна быть доступна по своим advertised.listeners. При переходе на KRaft важно убедиться, что клиенты обновлены до версий, поддерживающих все фичи нового кластера (например, очень старые клиенты может потребоваться обновить из соображений совместимости с версией брокера, но это стандартная практика при мейджор-апгрейдах Kafka).

Миграция кластера с ZooKeeper на KRaft

Переход существующего Kafka-кластера (версии 3.x) с ZooKeeper-режима на KRaft – ответственная процедура, требующая тщательного планирования. К счастью, в Kafka 3.5+ реализован механизм живой миграции метаданных без простоя (KIP-866, KIP-833 и др.). Общая стратегия миграции следующая:

  1. Подготовка: Обновите Kafka-кластер до версии, поддерживающей миграцию. Рекомендуется как минимум Kafka 3.5, а лучше 3.9, так как там устранены многие ограничения. Проверьте параметры inter.broker.protocol.version – он должен соответствовать целевой версии. Например, для миграции метаданных требуется IBP 3.5 или выше. Также узнайте текущий cluster.id вашего кластера – он понадобится новым контроллерам KRaft. Его можно взять из файла meta.properties на любом брокере или командой zookeeper-shell получить из ZK (но в ZK cluster.id может отсутствовать, надежнее meta.properties).
  2. Развертывание контроллеров KRaft: Нужно запустить новый кворум контроллеров параллельно с работающим ZooKeeper-кластером. Эти контроллеры будут работать в специальном режиме миграции. Выделите конфигурации для нескольких узлов, которые станут контроллерами (можно использовать существующие сервера или новые). Например, решаем иметь 3 контроллера. Выбираем для них уникальные node.id, которые не пересекаются с broker.id существующих брокеров (можно начать с 1000, или как в документации – 3000). Настраиваем у них process.roles=controller, прописываем controller.quorum.voters со значениями этих node.id@адрес:порт. Прописываем тот же cluster.id, что у существующего кластера (выполнив kafka-storage.sh format -t <clusterId> ... для директории контроллера). И самое главное – включаем флаг миграции: zookeeper.metadata.migration.enable=true, а также указываем, как подключиться к ZooKeeper: zookeeper.connect=<адрес ZK>. То есть контроллеры будут знать и про новый KRaft-кворум, и иметь доступ к старому ZK. После настройки – запускаем эти контроллеры. Они сформируют свой кворум, выберут лидера, но пока не содержат метаданных (журнал пуст). Однако благодаря флагу миграции, лидер-контроллер подключится к ZooKeeper и начнет оттуда читать текущее состояние метаданных кластера. Он последовательно выгрузит все топики, партиции, конфиги, ACL и т.д. из ZooKeeper и заполнит свой журнал метаданных теми же данными. Этот этап называется Bootstrap контроллеров. Лидер-контроллер записывает в свой журнал события REGISTER_BROKER_RECORD для всех брокеров, TOPIC_RECORD для всех топиков и т.п. – фактически сериализует ZK-состояние в виде Raft-лог записей. Эти записи реплицируются на остальных контроллеров (кворум).
  3. Переключение брокеров в режим миграции: Пока запущены новые контроллеры (в режиме миграции) и старый ZooKeeper-кластер, брокеры Kafka всё ещё работают в ZK-режиме и ничего не знают о новых контроллерах. Далее нужно постепенно подключить брокеры к контроллерам. Для этого на каждом брокере меняем конфигурацию:
    • Добавляем параметры контроллеров: controller.quorum.voters=<список> (те же, что и у контроллеров) и controller.listener.names=CONTROLLER (имя listener’а контроллеров).
    • Добавляем listener.security.protocol.map, если нужно, чтобы брокер понимал протокол CONTROLLER (например, CONTROLLER:PLAINTEXT).
    • Включаем и на брокере zookeeper.metadata.migration.enable=true – сигнал брокеру, что он участвует в миграции.
    • Брокер по-прежнему стартует с broker.id и zookeeper.connect (то есть он ещё не переключается на KRaft, а работает в гибридном режиме).
    • Также важно: зафиксировать inter.broker.protocol.version=3.5 (или вашу версию) на время миграции.
    Затем перезапускаем брокеры по одному (rolling restart) с новой конфигурацией. Каждый брокер, запустившись, всё ещё подключается к ZooKeeper (так как zookeeper.connect есть) и одновременно устанавливает соединение с новым контроллером (по controller.quorum.voters). Он передает контроллеру информацию о себе и начинает получать от него метаданные (контроллер уже наполнен журналом). Этот режим называется dual write / dual read: брокеры продолжают взаимодействовать со старым ZooKeeper-контроллером и уже получают команды от нового KRaft-контроллера. Новый контроллер в режиме миграции отправляет брокерам те же типы RPC, что раньше шел от ZK-контроллера (LeaderAndISR, UpdateMetadata), поддерживая синхронизацию. Когда все брокеры перезапущены с флагом миграции, контроллер KRaft получает сигнал, что все брокеры на борту. Он сравнивает свое состояние с ZooKeeper и фиксирует, что миграция метаданных завершена (в логах контроллера появится запись “Completed migration of metadata from Zookeeper to KRaft”). На этом этапе ZooKeeper уже не является источником данных – источником правды стал журнал KRaft, но брокеры ещё работают в режиме совместимости. ZooKeeper-кластер пока продолжает существовать, но по сути метаданные в нём заморожены (ведь все изменения уже идут через новый контроллер). Этот этап иногда называют KRaft dual-write: контроллер может и в ZK писать при необходимости для совместимости, но основной тракт – уже KRaft.
  4. Перевод брокеров на чистый KRaft-режим: Теперь, когда контроллерный кворум ведет метаданные, нужно перезапустить брокеры уже без ZooKeeper. Для этого на каждом брокере окончательно меняем конфиг:
    • Убираем broker.id (или заменяем на node.id с тем же числом).
    • Убираем строку zookeeper.connect (она более не нужна).
    • Убираем zookeeper.metadata.migration.enable (брокеру уже не нужен миграционный режим).
    • Добавляем process.roles=broker (чтобы брокер запустился именно как KRaft-брокер).
    • Сохраняем controller.quorum.voters и controller.listener.names (они должны остаться, чтобы брокер знал о контроллерах).
    • Удаляем/закомментируем inter.broker.protocol.version – в KRaft-режиме используется параметр metadata.version внутри контроллеров, IBP не применим.
    В общем, серверные проперти брокера становятся как у чистого KRaft (кроме того, что он не контроллер, а только брокер). Затем выполняем рестарт каждого брокера по одному, убедившись, что он поднимается и подключается к контроллеру. При запуске брокер теперь читает cluster ID из своего meta.properties (который был создан ранее, когда ZK-брокер работал; он уже содержит clusterId и broker.id, совпадающий с node.id) и подключается к контроллерам. В логах брокера должно быть видно, что он не подключается к ZooKeeper (строк про ZK нет), а устанавливает соединение с контроллером. После перевода всех брокеров – ZooKeeper больше не обслуживает Kafka.
  5. Выведение контроллеров из режима миграции: Остался финальный шаг – отключить режим миграции на контроллерах. Для этого на каждом контроллере останавливаем процесс и правим конфиг:
    • Комментируем или удаляем zookeeper.metadata.migration.enable=true.
    • Удаляем zookeeper.connect=... (больше не нужно).
    • Остальные параметры (process.roles=controller, voters и т.д.) оставляем без изменений.
    Перезапускаем контроллеры по одному. Теперь они запускаются как обычные KRaft-контроллеры без какой-либо привязки к ZooKeeper. Лидер контроллер продолжит работу, но уже с флагом миграции снятым – то есть кластер окончательно работает автономно. Можно убедиться, что контроллеры не обращаются к ZooKeeper (в логах или через netstat).
  6. Завершение: На этом миграция завершена – ZooKeeper больше не нужен и его кластер можно выключить. Рекомендуется понаблюдать за Kafka после миграции, проверить, что все метаданные (топики, ACL, конфиги) на месте, что можно создавать новые топики, изменять конфигурации, и т.д. После успешной проверки – остановить сервисы ZooKeeper.
  7. Риски и откат: Текущая реализация миграции (Kafka 3.5–3.9) не поддерживает обратного отката к ZooKeeper после завершения (нет возможности “передумать” и начать заново использовать ZK, кроме как восстанавливать кластер из бэкапа старого состояния). Поэтому действовать нужно осторожно. Если миграция прервана на середине (например, один брокер не переключился) – есть механизм rollback до определенной стадии, но он довольно сложен. В версии 3.5 миграция считалась Preview (т.е. для тестов), ожидается, что в 3.6 или 3.7 она станет полностью поддерживаемой. Официальная документация рекомендует: не мигрировать в продакшене, пока не уверены в стабильности, и обязательно протестировать на тестовом окружении. Также компания Confluent предлагает вариант миграции через двойной кластер (blue-green deploy): то есть развернуть новый KRaft-кластер параллельно и перенести продюсеров/консюмеров на него – такой путь хоть и сложнее, но более консервативен.

Обобщение по миграции: Важные моменты:

  • Без простоя: поддерживается сценарий без остановки всего кластера – миграция выполняется на горячую, брокеры поочередно перезапускаются. Клиенты в этот момент могут испытывать кратковременные перебои (например, на момент рестарта конкретного брокера), но Kafka сохранит работу.
  • Дополнительные ресурсы: На время миграции нужны дополнительные серверы (для контроллеров). После миграции можно было бы переиспользовать эти узлы как контроллеры или даже отключить лишние, но рекомендуется оставить как есть (3 контроллера – оптимально).
  • Версия и совместимость: Все брокеры должны быть одной версии в ходе миграции. Нельзя мигрировать брокеры разных версий. Также убедитесь, что все фичи, которые вы используете, поддерживаются в KRaft-режиме вашей версии (напр. если вы использовали ZooKeeper для чего-то особенного – но обычно все перенесено). В Kafka 3.5, например, не поддерживались динамические обновления некоторых настроек контроллера без рестарта – но к 3.8 это реализовали.
  • Пост-миграционная проверка: После перехода стоит выполнить тест: создать новый топик, убедиться, что он виден всем брокерам; перезапустить один контроллер и посмотреть, что другой стал лидером; проверить, что новые продюсеры/консюмеры подключаются и читают/пишут. Это подтвердит, что миграция прошла успешно.

В заключение: миграция – не тривиальная операция, но дается возможность сделать ее постепенно. Если кластер очень критичный, можно прибегнуть к помощи специалистов или подождать Kafka 4.0, где будет уже только KRaft (но тогда миграцию все равно придется делать – в виде обновления).

Преимущества KRaft (Kafka без ZooKeeper)

Ниже перечислим основные плюсы перехода на новую архитектуру KRaft, многие из которых уже прозвучали:

  • Упрощение инфраструктуры: исчезает необходимость развертывать и поддерживать отдельный ZooKeeper-кластер. Это снижает операционные затраты, устраняет целый класс возможных ошибок (несовместимость версий ZK, неправильные настройки, проблемы сетевого взаимодействия между Kafka и ZK и т.д.). DevOps-инженерам проще – одна система вместо двух, единый процесс развертывания.
  • Единая конфигурация и безопасность: настройки безопасности ZooKeeper (ACL, authentication) довольно обособлены от Kafka. В KRaft все аспекты безопасности (SASL, TLS) сконцентрированы на Kafka-брокерах и контроллерах, то есть администратор работает с едиными механизмами. Например, включить TLS – и для клиентских, и для контроллерных соединений это делается схожим образом в server.properties. Нет необходимости заводить отдельные логины/пароли для Kafka->ZooKeeper (как это бывало при включении SASL в ZooKeeper).
  • Производительность и масштабируемость: благодаря событийному журналу метаданных Kafka лучше масштабируется по числу топиков и партиций. Например, описанный ранее эффект долгого фейловера контроллера (пока тот перечитает 100k партиций из ZK) – в KRaft исчезает, новый контроллер готов сразу, так как у него уже все метаданные есть. Тесты показывают, что Kafka 3.x в режиме KRaft способна поддерживать гораздо большее число разделов на кластер, чем эквивалентный кластер с ZooKeeper, прежде чем начнутся проблемы с контроллером. Также сокращается нагрузка на сеть: при ZooKeeper-кластере очень большой трафик метаданных шел между контроллером и брокерами (линейно с числом брокеров), а ZooKeeper обрабатывал множество мелких запросов. Теперь обмен более эффективен (типа “нагоняющих” fetch’ей).
  • Согласованность метаданных: журнал обеспечивает строго упорядоченное применение изменений. Нет вероятности race condition при обновлении нескольких топиков сразу – все операции сериализуются в лог. Если раньше теоретически разные потоки/клиенты могли пытаться параллельно писать в ZooKeeper и возникали коллизии, то теперь Kafka сама последовательностью команд рулит, что упрощает поддержание консистентности.
  • Сокращение времени недоступности при сбоях: Разбор в разделе ZooKeeper показал, что при падении контроллера старая Kafka переживала паузу, пока новый загружал метаданные, что могло быть значительным при тысячах партиций. В KRaft эта пауза сильно уменьшена – новый лидер контроллер готов обслуживать запросы практически мгновенно, так как имеет журнал, и нужно лишь применить последние записи, если что-то не было применено (а это миллисекунды). То же при штатной перезагрузке контроллера (скажем, во время обновления версии) – кластер почти не ощущает этого. Это повышает общую отказоустойчивость и снижает время восстановления (RTO).
  • Удаление потенциального bottleneck: ZooKeeper был единой точкой отказа (Single Point of Failure) в том смысле, что его сбой делал весь Kafka-кластер некорректно функционирующим. Хотя ZooKeeper сам реплицирован (3 или 5 узлов), но если, например, человек ошибочно удалил данные ZK или случилась коррапция журнала ZK – остановился бы Kafka. В KRaft нет внешнего SPOF; контроллеры интегрированы, и сбой большей части из них, конечно, тоже остановит Kafka, но это один класс компонентов. Можно сказать, убрав ZooKeeper, Kafka устранила внешний SPOF и заменила его внутренним, более контролируемым.
  • Лучшая интеграция в облаке и Kubernetes: В Kubernetes-окружениях запуск ZooKeeper порой был сложным (StatefulSet с пятью подами, которые должны переживать сетевые разделы). KRaft-контроллеры – те же Kafka, их логика выбора лидера рассчитана на неблагоприятные условия. Операторы (например, Strimzi) уже внедряют поддержку KRaft, упрощая topologie. Также, например, для локальных развертываний (Docker Compose, testcontainers) – избавиться от ZK означает запустить на один контейнер меньше, быстрее прогреть Kafka при тестах.
  • Новые возможности развития Kafka: Переход на KRaft открывает дверь к ряду улучшений. Например, была сложность реализовать несколько контроллеров-координаторов для разных частей функциональности в старой архитектуре – все завязано на одном ZK-контроллере. Теперь несложно, например, иметь разделение обязанностей (в будущем): хотя сейчас один метаданные-контроллер ведет все, но архитектура более гибкая. Кроме того, администраторские утилиты могут улучшаться, используя единый интерфейс (AdminClient) для всего. Уже появляются идеи сделать контроллер более информированным о нагрузке (т.к. это брокерский процесс, имеющий общие метрики).

В целом, преимущество KRaft можно резюмировать так: Kafka стала автономной распределенной системой, не опирающейся на сторонний координационный сервис. Этот шаг похож на эволюцию некоторых других систем (например, отказ HBase от ZooKeeper, который планируется, или появление координации в самих базах данных). Для пользователей Kafka это означает проще развернуть, проще масштабировать, меньше точек отказа.

Конечно, ZooKeeper свое отслужил не зря – более десяти лет он помогал Kafka обеспечивать надежность. Но по мере роста нагрузок и требований стало очевидно, что собственный протокол консенсуса, специально заточенный под Kafka, даст выигрыши. Raft выбрали благодаря его понятийной простоте и гарантированной консистентности. Разработчики Kafka модифицировали классический Raft под свои нужды (pull-модель репликации, интеграция с существующим логом), но принципы остались те же.

Сравнительная таблица: Kafka с ZooKeeper vs Kafka с KRaft

Ниже приведена сводная таблица, сравнивающая две архитектуры по ключевым критериям:

КритерийKafka + ZooKeeper (до 3.x)Kafka KRaft (3.5+)
Хранение метаданныхВ ZooKeeper (external) – топики, партиции, конфиги, ACL хранятся в ZK узлах (znodes).Внутри Kafka (internal) – журнал __cluster_metadata на контроллерах хранит все метаданные.
Координация контроллераВыбор контроллера через ZooKeeper (епhemeral /controller node). Один активный контроллер, смена при сбое ~медленная (загрузка из ZK).Кворум контроллеров (Raft). Лидер избирается за миллисекунды среди контроллеров, уже владеющих данными (быстрый failover).
Зависимость от внешних системТребуется отдельный кластер ZooKeeper (обычно 3-5 узлов) – доп. зависимость, администрирование, мониторинг.Нет внешних зависимостей для метаданных – Kafka сама обеспечивает консенсус (только брокеры/контроллеры).
МасштабируемостьОграничена производительностью ZooKeeper: большие кластеры (1000+ партиций на брокер) испытывают проблемы (ZK-трафик, watcher-лимиты).Лучшая масштабируемость: протестировано на миллионах партиций (Confluent report). Метаданные реплицируются батчами, нет узкого места в виде единого ZK-сервера.
Метод распространения метаданныхPush-модель: контроллер генерирует UpdateMetadata RPC для каждого брокера при изменениях. Возможны рассинхроны, если брокер пропустил обновление.Pull-модель: брокеры сами периодически фетчат изменения у контроллера. Гарантировано последовательное применение по offset, никакой брокер не пропустит изменения (будет догонять).
КонсистентностьСильная консистентность ZooKeeper, но брокеры могли временно иметь старые данные до получения RPC. В целом eventual consistency для брокеров, возможны короткие несоответствия.Строгая последовательность журнала. Брокеры имеют eventual consistency с контроллером, но все изменения имеют глобальный порядок (offset). Нет возможности конфликтующих параллельных обновлений – упорядочены по log.
Отказоустойчивость метаданныхТребуется >= половины узлов ZK. Потеря ZK-кворума = Kafka недоступна. Восстановление после фейла контроллера может занимать секунды (чтение всего из ZK).Требуется >= половины контроллеров. Потеря кворума контроллеров = пауза. Но failover контроллера быстрый (у нового лидера уже все данные, recovery очень быстрый).
Обновление и упрощениеОбновление Kafka требует иногда отдельно обновлять ZooKeeper. Настройки разгрознены (Kafka vs ZK). Для разработчика – нужно помнить про ZK (например, приReset consumer group старые утилиты).Обновление Kafka = обновление всей системы (контроллеры и брокеры – тот же процесс). Нет необходимости координировать с внешним компонентом. Меньше мест для ошибок, единый способ управления (через Kafka Admin API).
Observability (наблюдение)Мониторинг двух систем: метрики Kafka + метрики ZooKeeper. Трассировка проблем сложнее (например, выяснить, что тормозит – контроллер или ZK?).Единый набор метрик Kafka, включая метрики контроллера/кворума. Инструменты типа Kafka UI теперь могут отображать состояние контроллеров, событий метаданных. Проще отслеживать, нет “черного ящика” ZK.
Совместимость клиентовКлиенты Kafka 0.10+ не используют ZK (только брокеры). Некоторые старые утилиты/клиенты (<0.10) требовали ZK, но их уже нет.То же для клиентов – им неважно KRaft или ZK. Требуется Kafka 3.x клиент, чтобы знать о новых фичах, но протокол совместим. Kafka Connect/Streams работают без изменений конфигов (bootstrap.servers).
Особенности/ограниченияПроверенная временем технология, но доп. сложности (ZNode size limit ~1MB, < 1M watchers, etc.). ZooKeeper хорошо отлажен, но Kafka использует небольшую часть его возможностей.Новая технология: в ранних версиях были ограничения (JBOD, dynamic configs – уже решены к 3.8). Требует миграции с ZK. После 4.0 альтернативы не будет. Но даёт пространство для дальнейшего развития Kafka.

Таблица 1: Сравнение Kafka с ZooKeeper и Kafka с KRaft.

Как видно, KRaft выигрывает по многим параметрам – особенно в упрощении и масштабируемости. Единственное, где ZooKeeper может восприниматься надежнее – это проверенность годами. Однако с выходом Kafka 3.5 и далее сообщество активно тестирует и улучшает KRaft, и он быстро набирает зрелость.

Заключение

Переход Apache Kafka от зависимости на ZooKeeper к собственной реализации консенсуса KRaft – одно из самых важных архитектурных изменений в истории проекта. Оно ликвидирует давнее “двойное дно” (две системы в одной) и делает Kafka более целостной, масштабируемой и удобной в эксплуатации. Мы рассмотрели, как работала Kafka с ZooKeeper, какие узкие места были присущи старому подходу, и детально разобрали новую архитектуру KRaft: от устройства кворума контроллеров до принципов алгоритма Raft и пошагового процесса миграции.

Для практикующих инженеров важно понимать, что хотя внутренности поменялись, базовые абстракции Kafka остались прежними: брокеры, топики, разделы, продюсеры/консюмеры – всё это продолжает работать так же, как раньше. Клиентам Kafka не нужно знать о том, есть ли ZooKeeper – и в будущем, начиная с Kafka 4.0, его не будет вовсе.

С точки зрения DevOps, планируя обновление Kafka, стоит заранее ознакомиться с процессом миграции. Если вы запускаете новый кластер – смело включайте KRaft-режим по умолчанию (в Kafka 3.3+ он уже считается продакшн-готовым). Если у вас большой устоявшийся кластер на ZooKeeper, можно либо пройти по описанной процедуре миграции на месте, либо рассмотреть стратегию параллельного развертывания нового кластера и переключения нагрузки.

KRaft приносит:

  • Упрощение (минус один сервис),
  • Повышение производительности и стабильности на больших масштабах,
  • Улучшенную отказоустойчивость контроллера,
  • Единообразие управления Kafka.

Конечно, необходимо обновить навыки мониторинга – например, следить за метриками контроллеров (количество online контроллеров, время выборов лидера и пр.). Но в целом, операционные практики остаются сходными: как и раньше, нужно заботиться о резервировании узлов, правильной конфигурации replication factor, регулярном обновлении версий Kafka.

Для команд, использующих Kafka, переход на новую архитектуру – возможность избавиться от ряда исторических ограничений и упростить поддержку системы. ZooKeeper сыграл свою роль, но время пришло передать эстафету новому механизму, более соответствующему потребностям сегодняшнего дня.

Следуя документации и лучшим практикам, миграция на KRaft может быть выполнена без серьезных проблем. И уже в ближайшем будущем Kafka-кластеры будут по умолчанию запускаться “standalone”, полагаясь лишь на собственный встроенный лог всех логов (The Log of All Logs, как поэтично назвали его авторы), что делает Kafka еще более привлекательной и надежной системой для потоковой обработки данных.

Loading