Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 16 из 25 ~18 мин +20 XP

Admin API: топики из кода

До сих пор топики появлялись в консоли: руками, чьей-то командой и чьей-то памятью. В kafka-go для этого есть отдельный слой — клиент kafka.Client с административными методами, который создаёт, описывает и удаляет топики прямо из кода. Разберём CreateTopics с TopicConfig — NumPartitions, ReplicationFactor и ConfigEntries с retention и min.insync.replicas, идемпотентное создание при старте сервиса с ошибкой TopicAlreadyExists, Metadata вместо ListTopics и DeleteTopics с картой ошибок, а затем обсудим границу: где автоматизация уместна, а где топики по-прежнему создают администраторы.

Содержание урока · 10 разделов

У Writer и Reader из прошлых уроков было общее ограничение: они работают с топиком, который уже существует. Если его нет, первый WriteMessages упадёт с UNKNOWN_TOPIC_OR_PARTITION, и сервис, ждущий топик от внешнего мира, окажется заложником чьей-то консоли. kafka-go решает это административным клиентом: структура kafka.Client с методами CreateTopics, Metadata, DeleteTopics и парой соседних закрывает полный жизненный цикл топика — от рождения до удаления, без выхода из Go-кода. Механика простая, но с характером: ответы приходят не одной ошибкой, а картой вердиктов по каждому имени, и половина багов вокруг admin API рождается из привычки ждать одну ошибку на весь запрос. Вторая тема урока — организационная: создание топиков из кода экономит минуты в разработке и ломает бюджеты в продакшене, если путать эти два режима. Число партиций, фактор репликации и ретеншен — решения с ценой, и место их принятия зависит от окружения.

kafka.Client: адрес, таймаут и transport#

kafka.Client — маленькая структура из трёх полей, и конструктора у неё нет: клиент создаётся литералом. Addr — адрес кластера или конкретного брокера типа net.Addr, его даёт хелпер kafka.TCP("localhost:9092"), знакомый по третьему уроку. Timeout — лимит на каждый административный запрос; ноль означает, что таймаута нет, и это плохой дефолт для продакшена. Transport — интерфейс RoundTripper, через который уходят запросы; при nil библиотека подставит DefaultTransport, но поле открывает дорогу общему транспорту с таймаутами и своим логгером для всего сервиса. Клиент не держит постоянных соединений: каждый вызов — отдельный round trip, поэтому создание одного объекта на весь процесс безопасно и дёшево. Важно понимать масштаб: Writer и Reader отвечают за данные, Client — за управление кластером, и последний ничего не знает про продюсирование. В третьем уроке литерал уже мелькал для коммитов оффсетов; здесь он становится полноправным инструментом. Одна деталь экономит час отладки: Timeout в клиенте не совпадает с таймаутами Writer и Reader — это отдельный лимит административных запросов, и он настраивается независимо, под свой SLA на управление кластером.

CreateTopics и TopicConfig: партиции и конфиги#

CreateTopics принимает CreateTopicsRequest с адресом и списком TopicConfig — одним вызовом можно создать сразу несколько топиков. В TopicConfig четыре рабочих поля: Topic — имя, NumPartitions — число партиций, ReplicationFactor — количество копий каждой партиции, ConfigEntries — брокерские настройки топика. Каждая настройка — это kafka.ConfigEntry с двумя строковыми полями, ConfigName и ConfigValue: протокол передаёт конфиги парами имя-значение, поэтому retention.ms записывается как "86400000", а min.insync.replicas из пятнадцатого урока — как "2". Есть и поле ValidateOnly: при true брокер проверит конфигурацию и не создаст ничего — сухой прогон для CI, который отчитывается, а не меняет кластер. Номер партиции, выбранной по ключу, зависит от NumPartitions, так что решение о числе партиций здесь всплывает снова — как в пятом уроке, только теперь вы его принимаете кодом. Ответ CreateTopicsResponse несёт карту Errors: имя топика указывает на error, и у каждого имени свой вердикт. Транспортная ошибка err означает, что запрос не состоялся вовсе, а вердикты внутри карты сравниваются через errors.Is с кодами пакета: TopicAlreadyExists, InvalidReplicationFactor, InvalidConfiguration.

Создаём платёжный топик с конфигурацией

GO
client := &kafka.Client{Addr: kafka.TCP("broker-1:9092")}

_, err := client.CreateTopics(ctx, &kafka.CreateTopicsRequest{
        Addr: client.Addr,
        Topics: []kafka.TopicConfig{
                {
                        Topic:             "payments",
                        NumPartitions:     6,
                        ReplicationFactor: 3,
                        ConfigEntries: []kafka.ConfigEntry{
                                {ConfigName: "min.insync.replicas", ConfigValue: "2"},
                                {ConfigName: "retention.ms", ConfigValue: "86400000"},
                        },
                },
        },
})

Адрес берётся из client.Addr — того же kafka.TCP, что и в третьем уроке. NumPartitions и ReplicationFactor — целые, а каждое значение ConfigEntry — строка: протокол передаёт конфиги парами имя-значение, и «2» здесь текст, а не число. Топик создаётся сразу с durability-минимумом из пятнадцатого урока и суточным ретеншеном, без последующей правки руками.

Проверка: частичный успех CreateTopics

CreateTopics вызван для двух топиков. Первый создан, у второго ReplicationFactor 4 при кластере из двух брокеров. Как выглядит ответ?

Идемпотентное создание при старте сервиса#

Сервис, который создаёт свои топики при каждом старте, рано или поздно запустится на окружении, где топик уже есть. Брокер ответит кодом TopicAlreadyExists — «a topic with this name already exists» — и наивная проверка if err != nil превратит второй деплой в аварию. Правильная трактовка: TopicAlreadyExists при создании — не сбой, а штатный исход, как nil, и для этого идемпотентная обёртка ensureTopic стала ритуалом при старте Go-сервисов. Два правила функции: создание идемпотентно, но конфигурацию оно не гарантирует, и проверка существования без сверки параметров — половина решения. Но у неё есть слепая зона, о которой стоит помнить: CreateTopics создаёт, но не обновляет. Если топик существует с одной партицией, а в запросе просят шесть, брокер вернёт тот же TopicAlreadyExists и оставит конфиг прежним — число партиций наращивается отдельным методом CreatePartitions, а конфиги существующего топика правятся через AlterConfigs. Поэтому честный ensureTopic после «уже существует» сверяет фактическую конфигурацию с желаемой через Metadata, иначе расхождение между ожиданиями кода и реальностью кластера обнаружится в продакшене, а не на деплое.

ensureTopic: создание как штатная операция

GO
func ensureTopic(ctx context.Context, c *kafka.Client, name string) error {
        resp, err := c.CreateTopics(ctx, &kafka.CreateTopicsRequest{
                Addr:   c.Addr,
                Topics: []kafka.TopicConfig{{Topic: name, NumPartitions: 6}},
        })
        if err != nil {
                return err // сеть, таймаут: запрос не дошёл
        }
        terr := resp.Errors[name]
        if terr == nil || errors.Is(terr, kafka.TopicAlreadyExists) {
                return nil // создан или уже жил — оба исхода рабочие
        }
        return terr // InvalidReplicationFactor, InvalidConfiguration и другие
}

Функция идемпотентна: вызывать её можно на каждом деплое. Транспортная ошибка err и вердикт топика terr — разные вещи: первая означает недошедший запрос, вторая — решение брокера по конкретному имени. TopicAlreadyExists трактуется как успех через errors.Is по коду, а не сравнением текста строки, которая в библиотеке может меняться.

Metadata: карта кластера вместо ListTopics#

Список топиков и их устройство в kafka-go достаются одним методом — client.Metadata. Запрос MetadataRequest несёт адрес и список имён; пустой список означает «весь кластер», конкретные имена — «опишите только их». Ответ MetadataResponse богаче CLI-вывода: ClusterID, контроллер кластера, список брокеров с адресами и идентификаторами, и главное — Topics, где у каждого топика есть имя, флаг Internal и массив партиций с лидерами и репликами. Флаг Internal помечает системные топики вроде __consumer_offsets, которые CLI иногда прячет, а вы в собственных скриптах должны фильтровать сами. Формально это один вызов на роль двух команд консоли: list topics и describe topics в нашей терминологии — ListTopics и DescribeTopics — собраны в Metadata. Ответ читается как карта: имя топика — ключ, а значение — его партиции с лидерами и репликами, и по ней удобно сверять фактический расклад с ожидаемым перед стартом группы консьюмеров из седьмого урока. В библиотеке есть и экспериментальный пакет topics с функцией List поверх него, но без обещаний обратной совместимости, поэтому прямые вызовы Metadata надёжнее для продакшена.

Проверка: Metadata с пустым списком

Что вернёт client.Metadata, если в MetadataRequest передать пустой список Topics?

Что вернёт CreateTopics на существующем топике?

Прочитайте код и выберите, что он выведет:

// Топик orders существует: одна партиция, RF 1.
// Сервис стартует и вызывает:
_, err := client.CreateTopics(ctx, &kafka.CreateTopicsRequest{
        Addr: client.Addr,
        Topics: []kafka.TopicConfig{
                {Topic: "orders", NumPartitions: 6, ReplicationFactor: 3},
        },
})

DeleteTopics и граница автоматизации#

DeleteTopics — самая короткая и самая опасная ручка клиента: DeleteTopicsRequest со списком имён, ответ с той же картой Errors, и топики с их данными исчезают без корзины — ни подтверждений, ни восстановления. Удаление асинхронно размечает партиции, и брокер ещё несколько секунд может отвечать на Metadata именем, которого больше нет. Из этой асимметрии рождается организационное правило урока. В разработке и CI код создаёт и чинит окружение сам: песочница поднимается, ensureTopic создаёт топики, тесты гоняются, всё удаляется одной командой — это легитимная автоматизация, ради которой admin API и писался. В продакшене топик — это бюджет: ReplicationFactor умножает хранилище, число партиций держит лимиты файловых дескрипторов брокеров, retention определяет срок жизни данных. Эти решения принимают люди или инфраструктура как код, а сервис при старте проверяет существование и падает с внятной ошибкой, если топика нет. DeleteTopics из кода сервиса в проде — красный флаг код-ревью почти всегда. Даже уборка тестовых окружений должна проходить через отдельный скрипт с явным списком имён, а не через общий конфиг сервиса.

Правила admin API одним списком#

  • kafka.Client — структура с Addr, Timeout и Transport: создаётся литералом, соединений не держит, один экземпляр на процесс.
  • CreateTopics создаёт, но не обновляет: существующее имя даёт TopicAlreadyExists, число партиций не меняется.
  • Вердикты по топикам лежат в Errors map[string]error; сравнение — errors.Is по кодам, не по тексту строки.
  • TopicAlreadyExists при старте — штатный исход идемпотентного создания, но конфигурацию после него надо сверить.
  • Metadata закрывает ListTopics и DescribeTopics: пустой список имён — карта всего кластера с Internal-топиками.
  • Граница автоматизации: dev и CI создают топики кодом, прод-конфигурацию утверждают админы или инфраструктура как код.

Методы Client для жизненного цикла топиков#

МетодЗапросОтвет
CreateTopicsCreateTopicsRequest{Topics []TopicConfig, ValidateOnly}Errors map[string]error — вердикт по каждому имени
MetadataMetadataRequest{Topics []string}ClusterID, контроллер, Brokers, Topics с партициями
DeleteTopicsDeleteTopicsRequest{Topics []string}Errors map[string]error; удаление необратимо
CreatePartitions, AlterConfigsЗапросы на существующие топикиНаращивание партиций и правка конфигов после создания

Соберите клиент и конфиг топика

Допишите хелпер адреса из третьего урока и имя конфига, задающего минимум синхронных реплик из пятнадцатого.

client := &kafka.Client{Addr: kafka.("broker-1:9092")} _, err := client.CreateTopics(ctx, &kafka.CreateTopicsRequest{ Addr: client.Addr, Topics: []kafka.TopicConfig{ { Topic: "payments", NumPartitions: 6, ReplicationFactor: 3, ConfigEntries: []kafka.ConfigEntry{ {ConfigName: "", ConfigValue: "2"}, }, }, }, })

Соберите идемпотентный ensureTopic

Расставьте строки: функция, вызов CreateTopics, проверка транспорта, чтение вердикта, успех на nil, успех на TopicAlreadyExists, возврат прочих ошибок.

Строки

Ваша программа

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

Карточки: клиент и топики

Термин

kafka.Client

нажмите, чтобы перевернуть
Определение

Структура с Addr, Timeout и Transport; создаётся литералом, запросы уходят через round trip, соединений не держит.

1 / 6

Admin API уложился в одну структуру и три метода: kafka.Client с адресом и транспортом, CreateTopics с TopicConfig для рождения топика, Metadata для его описания и DeleteTopics для финала, который лучше оставлять людям. Ключевые навыки урока — читать карту Errors по именам, относиться к TopicAlreadyExists как к успеху и помнить, что создание не равно обновлению: партиции и конфиги существующего топика правятся отдельными методами. Дальше по курсу: урок про retention и компакцию разберёт ConfigEntries глубже — что именно происходит с журналом, когда в cleanup.policy записано compact; урок про партиции напомнит, почему NumPartitions стоит зафиксировать до первого продюсера; урок про durability — где в этой истории min.insync.replicas; а урок про установку — тот самый Docker-стенд. Запустите цикл локально: создать, описать, удалить — пять минут практики дают больше, чем пересказ карты Errors. И помните границу урока: автоматизация администрирования начинается там, где заканчивается ответственность за бюджет кластера.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • Admin API kafka-go — это kafka.Client: Addr, Timeout, Transport; создаётся литералом, без конструктора.
  • CreateTopics принимает TopicConfig: имя, NumPartitions, ReplicationFactor и ConfigEntries со строковыми значениями.
  • Ответ — карта Errors по именам топиков; вердикты сравниваются errors.Is с кодами пакета, не с текстом.
  • TopicAlreadyExists при старте сервиса — штатный исход идемпотентного создания, а не сбой.
  • CreateTopics не обновляет существующий топик: партиции наращивает CreatePartitions, конфиги правит AlterConfigs.
  • Metadata закрывает ListTopics и DescribeTopics: пустой список имён возвращает карту всего кластера.
  • DeleteTopics необратим и возвращает ошибки по каждому имени; из кода сервиса в проде — красный флаг.
  • Гигиена: код создаёт топики в dev и CI, прод-конфигурацию утверждают админы или инфраструктура как код.

Домашнее задание

Поднимите локальный стенд из третьего урока и напишите ensureTopic: идемпотентное создание топика orders с NumPartitions 6 и ConfigEntries, задающими retention.ms на сутки, плюс сверку фактической конфигурации через Metadata, если брокер ответил TopicAlreadyExists. Проверьте сценарий расхождения: вручную создайте топик с одной партицией, запустите сервис и объясните по выводу Metadata, почему CreateTopics не исправил конфигурацию и каким методом её правят. Затем вызовите CreateTopics с ReplicationFactor 3 на одиночном брокере и убедитесь, что в Errors лежит InvalidReplicationFactor. Финал: удалите топик через DeleteTopics, сразу запросите Metadata и опишите, что брокер ответил в переходный период — и почему удаление из кода сервиса в продакшене требует отдельного обоснования.

Личные заметкиПусто
Хранятся только в вашем браузере (Kafka · Admin API: топики из кода)

Вопросы и ответы по уроку

Чем kafka.Client отличается от Writer и Reader — нельзя ли обойтись ими?

Writer и Reader работают с данными, ретраят и балансируют; Client работает с кластером: создаёт и удаляет топики, спрашивает карту брокеров, правит конфигурацию. Это разные протокольные API поверх одного транспорта, и смешивать их роли не стоит: продюсер не создаст топик, а клиент не отправит сообщение. На практике процесс держит Writer или Reader для данных и один Client для администрирования, и таймауты у них свои — под SLA данных и под SLA управления.

Почему CreateTopics не меняет партиции у существующего топика?

Так устроен протокол: CreateTopics отвечает только за рождение. Брокер защищает существующую конфигурацию от случайной перезаписи, ведь изменение числа партиций ломает раскладку по ключам из пятого урока — сообщения с тем же ключом начнут попадать в другие партиции. Для роста есть отдельный CreatePartitions, который увеличивает партиции, но не уменьшает, а настройки существующего топика правит AlterConfigs. Умный старт сервиса проверяет фактическое состояние через Metadata и вызывает именно нужный метод.

Как после TopicAlreadyExists убедиться, что конфигурация топика правильная?

Запросить Metadata с конкретным именем и сверить число партиций с ожидаемым; для глубокой сверки конфигов есть DescribeConfigs — он вернёт действующие значения вроде retention.ms и min.insync.replicas с пометкой, чьё это значение, брокера или топика. Проверять стоит и при первом старте: топик мог создать кто-то до вас, и его бюджет партиций и реплик — не ваш. Если сверка не сошлась, правильный путь — алерт и ручное решение, а не молчаливая попытка переопределить чужой топик.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Следующая ступень учебного пути

Ebitengine25 уроков~12 чпрактика25 уроков уже доступны

Ebitengine: 2D-игры на Go

Курс Ebitengine для Go: игровой цикл Update/Draw, спрайты и анимации, ввод, коллизии, тайлмапы, камера, звук, сцены и сборка на платформы. 25 уроков.

  • Понимать игровой цикл Update/Draw и Layout
  • Рисовать спрайты, атласы и примитивы
  • Обрабатывать ввод: клавиатура, мышь, тач
  • Строить движение, коллизии и анимации