Содержание урока · 10 разделов
- Kafka rebalance: триггеры и координатор
- Range и RoundRobin: стратегии раздачи партиций
- Таймеры группы: Heartbeat, Session, Rebalance
- Параметры группы kafka-go одним столом
- Подвисания: что видит и может код
- Чек-лист ребалансов одним списком
- Минимизация боли ребалансов
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
Деплой в три часа ночи, всё по регламенту: поды консьюмера биллинга ушли на новую версию один за другим. Через пять минут дежурный видит всплеск лага и разрозненные ошибки таймаутов; обработка то вставала, то ускорялась, дубликаты местами. Код не менялся, брокер здоров — изменилось число членов группы, а значит, каждую под-замену группа проходила ребаланс: заново делила партиции, перестраивала чтение, донастраивалась. Ребаланс — не баг и не экзотика, а штатный механизм Kafka, который срабатывает при каждом изменении состава группы. Но у него есть цена: во время перетасовки затронутые партиции не читаются, и чем чаще меняется состав, тем дороже жизнь. В этом уроке разберём, кто и как раздаёт партиции, какие стратегии раздачи предлагает kafka-go и как их выбирать, почему под может молчать тридцать секунд после смерти, и что ваш код может увидеть про ребаланс — ответ честный: не так много, и это осознанная простота клиента, а не недоработка курса.
Kafka rebalance: триггеры и координатор#
Соберём механику в одну цепочку. У каждой группы консьюмеров есть координатор — брокер, который ведёт членство: кто в группе, кто жив, кто как подписан. Ребаланс стартует, когда координатор видит изменение состава: новый член прислал JoinGroup, старый умер по таймауту сессии, член вышел, изменилась подписка или число партиций топика. Дальше координатор собирает всех живых членов, назначает одного лидером группы — не лидером партиции, а лидером именно группы, — и тот распределяет партиции между членами по выбранной стратегии. Результат фиксируется новой генерацией группы: номер поколения растёт на каждом ребалансе, и все участники знают, что внутреннее состояние прошлого поколения устарело. Номер поколения — не счётчик для красоты: он едет в каждом коммите оффсетов, и брокер отклоняет коммит от устаревшего поколения с ошибкой IllegalGeneration — «призрак» прошлой раздачи может ещё успеть прочитать пару сообщений, но подтвердить их новый владелец партиции уже не позволит. Отсюда свойства, которые важно прочувствовать: ребаланс — событие всей группы, а не одной партиции; партиции, которые не переезжают, всё равно проходят через новую генерацию; и на время раздачи чтение приостанавливается. Триггеры делятся на плановые — деплой и скейлинг — и внеплановые: падение пода, сетевые разрывы, таймауты. Плановые лечатся архитектурой процесса, внеплановые — таймерами группы, до которых дойдёт очередь.
Reader с явным списком стратегий
GOreader := kafka.NewReader(kafka.ReaderConfig{
GroupID: "billing",
Brokers: []string{"localhost:9092"},
Topic: "payments",
GroupBalancers: []kafka.GroupBalancer{
kafka.RangeGroupBalancer{},
kafka.RoundRobinGroupBalancer{},
},
})
defer reader.Close()
m, err := reader.ReadMessage(ctx)Поле GroupBalancers — приоритетный список стратегий, которые этот reader предлагает координатору на переговорах: первая, поддержанная всеми членами группы, будет выбрана лидером. Здесь reader называет range первым и roundrobin запасным. Без явного поля kafka-go подставит тот же набор по умолчанию — [Range, RoundRobin]; явная запись нужна там, где список меняют или документируют контракт группы.
Range и RoundRobin: стратегии раздачи партиций#
kafka-go предлагает три стратегии, и переговоры между членами идут по имени протокола, а не по типу Go-структуры. RangeGroupBalancer представляется именем «range», RoundRobinGroupBalancer — «roundrobin»; третий вариант, RackAffinityGroupBalancer, требует настройки rack и решает отдельную задачу привязки читателей к стойкам. Раздача у простых двух разная. Range режет непрерывные диапазоны: шесть партиций на трёх членов дают первому партиции ноль и один, второму два и три, третьему четыре и пять. RoundRobin крутит по кругу: на той же шестёрке и тройке первый член получает ноль и три, второй — один и четыре, третий — два и пять. Разница проявляется на мультитопиковых группах и на неравномерных потоках: range сохраняет диапазоны, roundrobin перемешивает. Выбор делается переговорами: leader группы берёт первую стратегию из списка, которую поддерживают все члены, — поэтому приоритетный порядок в GroupBalancers значим, а разнородные клиенты в одной группе могут незаметно откатить вас на общий знаменатель. Практический совет: для однотопиковой группы выбор почти не важен, для мультитопиковой — тестируйте обе стратегии на своих числах, а не на чужих скриншотах.
Проверка: раздача по Range
Топик на шесть партиций, три консьюмера, стратегия Range. Кто что получит?
Что получит первый консьюмер при RoundRobin?
Прочитайте код и выберите, что он выведет:
// топик на 6 партиций, группа из трёх консьюмеров
// GroupBalancers: []kafka.GroupBalancer{kafka.RoundRobinGroupBalancer{}}
// партиции первого консьюмера?Тайная цена rolling-деплоя: kafka-go при Close не отправляет брокеру явного выхода из группы — координатор снимает членство, когда истекает SessionTimeout, по умолчанию тридцать секунд. До этого момента мёртвый под считается живым членом и держит свои партиции: они не читаются, а новые члены ждут новой генерации. Результат в проде — ступеньки по тридцать секунд на каждую замену пода и всплеск лага после каждого релиза. Рецепты три: принять как данность для нечувствительных потоков; осознанно ужать SessionTimeout и HeartbeatInterval под своим балансом риска — короткие таймеры чаще выкидывают живых на паузах; или разносить релизы так, чтобы всплеск лага попадал в безопасное окно. Важно понимать механику, а не лечить симптом: сорок секунд тишины после каждой под-замены — это не сеть, это контракт группы.
Таймеры группы: Heartbeat, Session, Rebalance#
Жизнью группы управляют три таймера, и путать их — классическая ошибка. HeartbeatInterval задаёт частоту пульсов: член группы по умолчанию рапортует координатору каждые три секунды, что жив. SessionTimeout — срок, после которого тишина без пульсов считается смертью: тридцать секунд по умолчанию; если пульсов нет дольше, координатор выкидывает члена и стартует ребаланс. RebalanceTimeout — другое измерение: сколько координатор готов ждать перерегистрации живых членов на уже начавшемся ребалансе, тоже тридцать секунд по умолчанию; медленный консьюмер, который не успел вернуться за срок, вылетает из новой генерации. Есть и четвёртая ручка, JoinGroupBackoff: пауза в пять секунд перед повторной попыткой входа после неудачи — она спасает от шторма присоединений при кратковременном сбое брокера. Настройка — баланс: короткие таймеры быстрее замечают смерть и быстрее пересобираются, но чаще выкидывают живых членов, у которых была пауза в сборке мусора или в сети. Дефолты разумны для сервисных потоков; менять их стоит по измеренному времени обработки и профилю пауз, а не потому, что тридцать секунд звучит долго.
Параметры группы kafka-go одним столом#
| Параметр ReaderConfig | Дефолт | Что управляет |
|---|---|---|
| GroupBalancers | [Range, RoundRobin] | Приоритетный список стратегий: первую поддержанную всеми выберет лидер |
| HeartbeatInterval | 3 секунды | Частота пульсов члена группы координатору |
| SessionTimeout | 30 секунд | Тишина дольше срока — член мёртв, стартует ребаланс |
| RebalanceTimeout | 30 секунд | Сколько координатор ждёт перерегистрацию членов на ребалансе |
| JoinGroupBackoff | 5 секунд | Пауза перед повторной попыткой входа после неудачи |
Проверка: оффсеты после переезда
Партиция переехала от консьюмера A к консьюмеру B на ребалансе. С какого места B начнёт читать?
Наблюдение за ребалансом из кода
GOreader := kafka.NewReader(kafka.ReaderConfig{
GroupID: "billing",
Brokers: []string{"localhost:9092"},
Topic: "payments",
Logger: log.New(os.Stdout, "reader: ", 0),
})
go func() {
for range time.Tick(10 * time.Second) {
s := reader.Stats()
log.Printf("rebalances=%d", s.Rebalances)
}
}()Два прибора честного наблюдения. Logger печатает внутренние события reader'а — входы в группу, синхронизации, пульсы — и на деплое видно сам ребаланс в реальном времени. Stats().Rebalances — счётчик перетасовок: рост на графике совпадает с релизами и падениями, а ровная линия означает стабильную группу. Больше ничего в коде ребаланс о себе не сообщает — это не упущение, а контракт клиента.
Подвисания: что видит и может код#
У ребаланса в kafka-go нет колбэка — в отличие от Java-клиента с его rebalance listeners, ваш код не получает вызов «партиции забрали» или «партиции выдали». Это осознанная простота: во время перетасовки ReadMessage просто ждёт дольше обычного, а после неё продолжает с новым набором партиций. Что остаётся программисту: Logger reader'а, счётчик Rebalances в Stats и мониторинг лага — они дают полную картину постфактум. Подвисания на ребалансе распознаются по почерку: чтение стоит у всех членов группы разом — ребаланс; стоит у одного и лаг растёт по его партициям — завис член группы, и скоро его выкинет по SessionTimeout. Практика длинных обработок: пульсы идут в фоне внутри reader, поэтому долгая обработка одного сообщения сама по себе пульсы не глушит; риск в другом — если обработка переживает начавшийся ребаланс, партиция может переехать до коммита, и работа повторится у нового владельца. О будущем: sticky-стратегия, уменьшающая переезды, есть в протоколе и в других клиентах, но в kafka-go v0.4.49 её нет — следите за changelog, а не пишите свою.
Закоммиченные оффсеты переживают ребаланс — они лежат в брокере. А вот стэш интервальных коммитов внутри reader — нет: при смене генерации он пересоздаётся, и накопленные, но не отправленные коммиты гибнут. Для потока с CommitInterval в секунду это значит: до секунды подтверждённой обработки может быть переобработано новым владельцем партиции. Отсюда два следствия: критичные потоки коммитят синхронно или часто, а ретраи-дубликаты после ребаланса — штатная ситуация, и дедупликация из девятого урока не опция, а база. Коммит после Close возвращает io.ErrClosedPipe — финальный коммит делайте до закрытия, как в восьмом уроке.
Чек-лист ребалансов одним списком#
- Размер группы по числу партиций: члены без партиций — простаивающие инстансы и лишние перетасовки.
- Плановый деплой — плановый ребаланс: разносите под-замены и следите за лагом после релиза.
- SessionTimeout и HeartbeatInterval меняйте парой и по измерениям, а не по ощущениям.
- Rebalances в Stats — график на дашборд: всплески должны совпадать с известными событиями.
- Коммиты переживают ребаланс, стэш интервальных коммитов — нет: критичные потоки коммитят чаще.
- Sticky-стратегии в kafka-go v0.4.49 нет: не выдумывайте свою, следите за changelog клиента.
Минимизация боли ребалансов#
Полностью убрать ребалансы нельзя — можно сделать их редкими, короткими и нестрашными. Редкими: стабильный размер группы, деплои по расписанию, не меняющие состав чаще необходимого, и никаких рестартов по крону без причины. Короткими: здоровые члены, которые быстро перерегистрируются, — таймеры по умолчанию рассчитаны на обычное приложение, и если ваш обработчик тратит полминуты на вход, проблема не в таймере. Нестрашными: дедупликация на потребителе, коммиты по дисциплине из восьмого урока и идемпотентные обработчики — тогда переезд партиции стоит вам переобработки хвоста, а не инцидента. Отдельно про скейлинг: добавление члена в группу с шестью партициями и шестью членами ничего не ускорит — седьмой простаивает; масштабируйте вместе с числом партиций, о чём пятый урок говорил со стороны ключей. И последнее: kafka rebalance — это не только клиентская история, у брокера есть свои пороги и лимиты группы; но в рамках go-кода вы управляете списком стратегий, таймерами и дисциплиной коммитов — этого достаточно, чтобы жить в ладу с перетасовками.
Картина ребаланса собрана: триггеры, координатор, стратегии, таймеры и два прибора наблюдения. Дальше по курсу: седьмой урок про группы напомнит, как группы устроены базово; восьмой урок про коммиты покажет, что именно переезжает вместе с партицией; двадцать первый урок про мониторинг лага научит видеть последствия ребалансов на графиках. Формат честности прежний: для живого эксперимента нужен локальный брокер из Docker и два инстанса программы — поднимите их и поубивайте, глядя в Rebalances; интерактивы ниже разыгрывают механику раздачи и таймеров на чистом Go — песочница справляется с этим без сети и брокера.
Соберите reader с настройками группы
Допишите стратегию Range и частоту пульсов.
Соберите группового reader
Расставьте строки: конфиг с GroupID и стратегией, закрытие, первое чтение.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: ребалансировка
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Kafka rebalance перетасовывает партиции: триггеры — вход, выход и тишина членов, смена подписок и числа партиций.
- Стратегии выбираются переговорами: лидер берёт первую из GroupBalancers, поддержанную всеми членами.
- Range режет непрерывные диапазоны, RoundRobin раздаёт по кругу: на шестёрке и тройке это 0-1-2-3-4-5 против 0-3-1-4-2-5.
- SessionTimeout 30 секунд: Close не уведомляет группу, мертвец держит партиции до таймаута.
- Коммиты переживают ребаланс, стэш интервальных коммитов — нет: критичные потоки коммитят чаще.
- Колбэка ребаланса в kafka-go нет: смотреть Logger и Stats().Rebalances.
- Ребалансировка go-группы делается редкой и нестрашной: стабильный состав, парные таймеры, дедупликация.
Домашнее задание
Поднимите брокер из третьего урока, напишите консьюмера с GroupID и логом Stats().Rebalances раз в секунду. Запустите второй инстанс той же программы с тем же GroupID и зафиксируйте ребаланс в логах: кто какие партиции забрал — напечатайте m.Partition при обработке. Затем отправьте первый инстанс SIGKILL и засеките, через сколько секунд группа заметила смерть и запустила новую генерацию. Финал: уменьшите SessionTimeout до десяти секунд и повторите — запишите разницу и решение, стоит ли так делать в проде. Заодно пройдите взглядом по логу Logger reader'а: найдите строку о выбранной стратегии раздачи и номер поколения группы — на живой группе они видны лучше любой схемы из урока.
Личные заметкиПусто
Вопросы и ответы по уроку
Можно ли повесить обработчик на ребаланс, как в Java-клиенте?
В kafka-go v0.4.49 колбэков ребаланса нет: ReadMessage просто дольше ждёт во время перетасовки. Наблюдение — Logger reader'а и счётчик Rebalances в Stats. Если нужна реакция на смену партиций — сброс локального состояния по факту ошибок чтения или периодическая сверка Assignments, — это делается на вашей стороне, но чаще правильнее упростить обработчик до идемпотентного.
Почему после убийства пода группа тридцать секунд молчит?
Потому что смерть замечается по SessionTimeout: kafka-go при Close не отправляет выход из группы, и координатор снимает членство только после тишины в тридцать секунд. Партиции мертвеца не читаются всё это время. Лечится осознанной настройкой SessionTimeout и HeartbeatInterval парой либо планированием деплоя с учётом паузы.
Какая стратегия лучше: Range или RoundRobin?
Для однотопиковой группы с ровным потоком разница почти не видна: обе делят партиции по числу членов. Range сохраняет непрерывные диапазоны и удобна, когда партиции тематические; RoundRobin перемешивает и лучше выравнивает нагрузку на мультитопиковых подписках. Выбор фиксируется переговорами по имени протокола: следите, чтобы все члены группы предлагали одну стратегию первой, иначе лидер откатит группу на общий знаменатель. Проверить фактический результат просто: Logger reader'а на входе в группу печатает строку вида «Joined group ... in generation N» — из неё видно и номер поколения, на котором произошла последняя раздача.
Термины курса
Понятия, на которых строится этот курс, — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылок на практику.
Изучайте дальше
Следующая ступень учебного пути
Ebitengine: 2D-игры на Go
Курс Ebitengine для Go: игровой цикл Update/Draw, спрайты и анимации, ввод, коллизии, тайлмапы, камера, звук, сцены и сборка на платформы. 25 уроков.
- Понимать игровой цикл Update/Draw и Layout
- Рисовать спрайты, атласы и примитивы
- Обрабатывать ввод: клавиатура, мышь, тач
- Строить движение, коллизии и анимации