Содержание урока · 10 разделов
- kafka.Client: адрес, таймаут и transport
- CreateTopics и TopicConfig: партиции и конфиги
- Идемпотентное создание при старте сервиса
- Metadata: карта кластера вместо ListTopics
- DeleteTopics и граница автоматизации
- Правила admin API одним списком
- Методы Client для жизненного цикла топиков
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
У 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.
Создаём платёжный топик с конфигурацией
GOclient := &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: создание как штатная операция
GOfunc 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 по коду, а не сравнением текста строки, которая в библиотеке может меняться.
Инцидент на двух сервисах: каждый создавал общий топик events при старте, и оба молча глотали TopicAlreadyExists как «норму». Первый деплой создал топик с одной партицией; второй сервис ждал шесть и распределял нагрузку по ключам из пятого урока — на одной партиции всё легло в очередь, а никто из авторов не заметил, потому что ошибка «уже существует» считалась безопасной. Урок двойной: молчать стоит только после сверки конфигурации, а уникальное имя партиций — часть контракта сервиса. Бонус-ловушка того же семейства: CreateTopics не увеличивает партиции, и «починить» это повторным вызовом нельзя — нужен CreatePartitions.
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 из кода сервиса в проде — красный флаг код-ревью почти всегда. Даже уборка тестовых окружений должна проходить через отдельный скрипт с явным списком имён, а не через общий конфиг сервиса.
Административные методы приятно осваивать локально: Docker-стенд из третьего урока переживёт сколько угодно CreateTopics и DeleteTopics, и песочница — единственное место, где удаление топиков можно вызвать просто из любопытства. Цикл эксперимента: создайте топик, найдите его в Metadata, удалите, убедитесь, что он исчез. Честное ограничение стенда: ReplicationFactor выше числа брокеров невозможен — на одиночном брокере запрос с RF 3 вернёт InvalidReplicationFactor, и это лучший способ увидеть код своими глазами, а не в тексте урока.
Правила admin API одним списком#
- kafka.Client — структура с Addr, Timeout и Transport: создаётся литералом, соединений не держит, один экземпляр на процесс.
- CreateTopics создаёт, но не обновляет: существующее имя даёт TopicAlreadyExists, число партиций не меняется.
- Вердикты по топикам лежат в Errors map[string]error; сравнение — errors.Is по кодам, не по тексту строки.
- TopicAlreadyExists при старте — штатный исход идемпотентного создания, но конфигурацию после него надо сверить.
- Metadata закрывает ListTopics и DescribeTopics: пустой список имён — карта всего кластера с Internal-топиками.
- Граница автоматизации: dev и CI создают топики кодом, прод-конфигурацию утверждают админы или инфраструктура как код.
Методы Client для жизненного цикла топиков#
| Метод | Запрос | Ответ |
|---|---|---|
| CreateTopics | CreateTopicsRequest{Topics []TopicConfig, ValidateOnly} | Errors map[string]error — вердикт по каждому имени |
| Metadata | MetadataRequest{Topics []string} | ClusterID, контроллер, Brokers, Topics с партициями |
| DeleteTopics | DeleteTopicsRequest{Topics []string} | Errors map[string]error; удаление необратимо |
| CreatePartitions, AlterConfigs | Запросы на существующие топики | Наращивание партиций и правка конфигов после создания |
Соберите клиент и конфиг топика
Допишите хелпер адреса из третьего урока и имя конфига, задающего минимум синхронных реплик из пятнадцатого.
Соберите идемпотентный ensureTopic
Расставьте строки: функция, вызов CreateTopics, проверка транспорта, чтение вердикта, успех на nil, успех на TopicAlreadyExists, возврат прочих ошибок.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: клиент и топики
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.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 с пометкой, чьё это значение, брокера или топика. Проверять стоит и при первом старте: топик мог создать кто-то до вас, и его бюджет партиций и реплик — не ваш. Если сверка не сошлась, правильный путь — алерт и ручное решение, а не молчаливая попытка переопределить чужой топик.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
Следующая ступень учебного пути
Ebitengine: 2D-игры на Go
Курс Ebitengine для Go: игровой цикл Update/Draw, спрайты и анимации, ввод, коллизии, тайлмапы, камера, звук, сцены и сборка на платформы. 25 уроков.
- Понимать игровой цикл Update/Draw и Layout
- Рисовать спрайты, атласы и примитивы
- Обрабатывать ввод: клавиатура, мышь, тач
- Строить движение, коллизии и анимации