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

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

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

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

Ребалансировка группы

Kafka rebalance — перетасовка партиций между членами группы, и ребалансировка go-сервисов чаще всего въезжает на деплоях: подскейлился инстанс, упал под — и группа заново делит топик. Разберём стратегии Range и RoundRobin, таймеры группы, подвисания и наблюдение за ребалансом из кода.

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

Деплой в три часа ночи, всё по регламенту: поды консьюмера биллинга ушли на новую версию один за другим. Через пять минут дежурный видит всплеск лага и разрозненные ошибки таймаутов; обработка то вставала, то ускорялась, дубликаты местами. Код не менялся, брокер здоров — изменилось число членов группы, а значит, каждую под-замену группа проходила ребаланс: заново делила партиции, перестраивала чтение, донастраивалась. Ребаланс — не баг и не экзотика, а штатный механизм Kafka, который срабатывает при каждом изменении состава группы. Но у него есть цена: во время перетасовки затронутые партиции не читаются, и чем чаще меняется состав, тем дороже жизнь. В этом уроке разберём, кто и как раздаёт партиции, какие стратегии раздачи предлагает kafka-go и как их выбирать, почему под может молчать тридцать секунд после смерти, и что ваш код может увидеть про ребаланс — ответ честный: не так много, и это осознанная простота клиента, а не недоработка курса.

Kafka rebalance: триггеры и координатор#

Соберём механику в одну цепочку. У каждой группы консьюмеров есть координатор — брокер, который ведёт членство: кто в группе, кто жив, кто как подписан. Ребаланс стартует, когда координатор видит изменение состава: новый член прислал JoinGroup, старый умер по таймауту сессии, член вышел, изменилась подписка или число партиций топика. Дальше координатор собирает всех живых членов, назначает одного лидером группы — не лидером партиции, а лидером именно группы, — и тот распределяет партиции между членами по выбранной стратегии. Результат фиксируется новой генерацией группы: номер поколения растёт на каждом ребалансе, и все участники знают, что внутреннее состояние прошлого поколения устарело. Номер поколения — не счётчик для красоты: он едет в каждом коммите оффсетов, и брокер отклоняет коммит от устаревшего поколения с ошибкой IllegalGeneration — «призрак» прошлой раздачи может ещё успеть прочитать пару сообщений, но подтвердить их новый владелец партиции уже не позволит. Отсюда свойства, которые важно прочувствовать: ребаланс — событие всей группы, а не одной партиции; партиции, которые не переезжают, всё равно проходят через новую генерацию; и на время раздачи чтение приостанавливается. Триггеры делятся на плановые — деплой и скейлинг — и внеплановые: падение пода, сетевые разрывы, таймауты. Плановые лечатся архитектурой процесса, внеплановые — таймерами группы, до которых дойдёт очередь.

Reader с явным списком стратегий

GO
reader := 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{}}
// партиции первого консьюмера?

Таймеры группы: Heartbeat, Session, Rebalance#

Жизнью группы управляют три таймера, и путать их — классическая ошибка. HeartbeatInterval задаёт частоту пульсов: член группы по умолчанию рапортует координатору каждые три секунды, что жив. SessionTimeout — срок, после которого тишина без пульсов считается смертью: тридцать секунд по умолчанию; если пульсов нет дольше, координатор выкидывает члена и стартует ребаланс. RebalanceTimeout — другое измерение: сколько координатор готов ждать перерегистрации живых членов на уже начавшемся ребалансе, тоже тридцать секунд по умолчанию; медленный консьюмер, который не успел вернуться за срок, вылетает из новой генерации. Есть и четвёртая ручка, JoinGroupBackoff: пауза в пять секунд перед повторной попыткой входа после неудачи — она спасает от шторма присоединений при кратковременном сбое брокера. Настройка — баланс: короткие таймеры быстрее замечают смерть и быстрее пересобираются, но чаще выкидывают живых членов, у которых была пауза в сборке мусора или в сети. Дефолты разумны для сервисных потоков; менять их стоит по измеренному времени обработки и профилю пауз, а не потому, что тридцать секунд звучит долго.

Параметры группы kafka-go одним столом#

Параметр ReaderConfigДефолтЧто управляет
GroupBalancers[Range, RoundRobin]Приоритетный список стратегий: первую поддержанную всеми выберет лидер
HeartbeatInterval3 секундыЧастота пульсов члена группы координатору
SessionTimeout30 секундТишина дольше срока — член мёртв, стартует ребаланс
RebalanceTimeout30 секундСколько координатор ждёт перерегистрацию членов на ребалансе
JoinGroupBackoff5 секундПауза перед повторной попыткой входа после неудачи

Проверка: оффсеты после переезда

Партиция переехала от консьюмера A к консьюмеру B на ребалансе. С какого места B начнёт читать?

Наблюдение за ребалансом из кода

GO
reader := 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, а не пишите свою.

Чек-лист ребалансов одним списком#

  • Размер группы по числу партиций: члены без партиций — простаивающие инстансы и лишние перетасовки.
  • Плановый деплой — плановый ребаланс: разносите под-замены и следите за лагом после релиза.
  • SessionTimeout и HeartbeatInterval меняйте парой и по измерениям, а не по ощущениям.
  • Rebalances в Stats — график на дашборд: всплески должны совпадать с известными событиями.
  • Коммиты переживают ребаланс, стэш интервальных коммитов — нет: критичные потоки коммитят чаще.
  • Sticky-стратегии в kafka-go v0.4.49 нет: не выдумывайте свою, следите за changelog клиента.

Минимизация боли ребалансов#

Полностью убрать ребалансы нельзя — можно сделать их редкими, короткими и нестрашными. Редкими: стабильный размер группы, деплои по расписанию, не меняющие состав чаще необходимого, и никаких рестартов по крону без причины. Короткими: здоровые члены, которые быстро перерегистрируются, — таймеры по умолчанию рассчитаны на обычное приложение, и если ваш обработчик тратит полминуты на вход, проблема не в таймере. Нестрашными: дедупликация на потребителе, коммиты по дисциплине из восьмого урока и идемпотентные обработчики — тогда переезд партиции стоит вам переобработки хвоста, а не инцидента. Отдельно про скейлинг: добавление члена в группу с шестью партициями и шестью членами ничего не ускорит — седьмой простаивает; масштабируйте вместе с числом партиций, о чём пятый урок говорил со стороны ключей. И последнее: kafka rebalance — это не только клиентская история, у брокера есть свои пороги и лимиты группы; но в рамках go-кода вы управляете списком стратегий, таймерами и дисциплиной коммитов — этого достаточно, чтобы жить в ладу с перетасовками.

Картина ребаланса собрана: триггеры, координатор, стратегии, таймеры и два прибора наблюдения. Дальше по курсу: седьмой урок про группы напомнит, как группы устроены базово; восьмой урок про коммиты покажет, что именно переезжает вместе с партицией; двадцать первый урок про мониторинг лага научит видеть последствия ребалансов на графиках. Формат честности прежний: для живого эксперимента нужен локальный брокер из Docker и два инстанса программы — поднимите их и поубивайте, глядя в Rebalances; интерактивы ниже разыгрывают механику раздачи и таймеров на чистом Go — песочница справляется с этим без сети и брокера.

Соберите reader с настройками группы

Допишите стратегию Range и частоту пульсов.

reader := kafka.NewReader(kafka.ReaderConfig{ GroupID: "billing", Brokers: []string{"localhost:9092"}, Topic: "payments", GroupBalancers: []kafka.GroupBalancer{kafka.GroupBalancer{}}, : 3 * time.Second, })

Соберите группового reader

Расставьте строки: конфиг с GroupID и стратегией, закрытие, первое чтение.

Строки

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

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

Карточки: ребалансировка

Термин

Kafka rebalance

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

Перетасовка партиций между членами группы: стартует при входе, выходе или тишине члена и при смене партиций.

1 / 6

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

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +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'а: найдите строку о выбранной стратегии раздачи и номер поколения группы — на живой группе они видны лучше любой схемы из урока.

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

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

Можно ли повесить обработчик на ребаланс, как в 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» — из неё видно и номер поколения, на котором произошла последняя раздача.

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

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

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

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

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

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

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