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

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

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

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

Reader API: чтение с оффсета

Kafka setoffset — прямой доступ к позиции чтения: reader без группы сам решает, с какого оффсета начать, и умеет перепрыгивать в любое место партиции. Разберём Reader API kafka: SetOffset с FirstOffset и LastOffset, SetOffsetAt по времени, ReadLag и случаи, когда группа только мешает.

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

Задача вне штатного потока: группа закоммитила всё до последнего сообщения и уехала вперёд, а вам нужно перечитать вчерашние события — миграция данных, расследование инцидента, пересчёт агрегата. Группа для этого не приспособлена: её позиция принадлежит ей, и менять её по ходу дела нечестно по отношению к остальным членам. Для таких задач у kafka-go есть второй режим Reader — без GroupID, с прямым указанием партиции и полным контролем позиции. Это тот же Reader из шестого урока, но в режиме «одна партиция, один владелец»: вы задаёте стартовую точку, двигаете её когда угодно между чтениями и видите лаг партиции без посредников. В этом уроке разберём конфигурацию без группы, все формы kafka setoffset — константы, числа и время, — измерения Offset и ReadLag и честный список сценариев, где прямой доступ к партиции выигрывает у группы, и где проигрывает.

Reader API без группы: Partition вместо GroupID#

Конфигурация зеркальна групповой: в ReaderConfig вместо GroupID указывается Partition — конкретный номер партиции топика. Правило взаимной исключённости жёсткое: NewReader паникует, если заданы и Partition, и GroupID одновременно — документация формулирует «either Partition or GroupID may be specified, but not both», и паника на старте это лучший способ узнать о конфликте. Один reader в этом режиме читает ровно одну партицию: параллелизм получаете вручную, по reader на партицию, и распределение партиций по инстансам — тоже ваша работа, а не работа брокера. Взамен исчезают все групповые механики: никаких пульсов, генераций и перетасовок; процесс умер — чтение остановилось, поднялся — продолжили с той позиции, которую сами установили. Для разовой стартовой точки есть и поле конфига StartOffset — оно принимает те же константы FirstOffset и LastOffset, его дефолт FirstOffset, и оно экономит вызов SetOffset, когда позиция известна ещё до создания reader'а. Именно за предсказуемость groupless-режим любят в утилитах: реплей-джобы, аудиты, переливы между топиками. Цена — самостоятельность: никто не закоммитит позицию за вас, и если сервис упал, продолжение с места остановки нужно организовать явно — хранить оффсет там же, где и результат обработки.

Партиционный reader с перемоткой на начало

GO
reader := kafka.NewReader(kafka.ReaderConfig{
        Brokers:   []string{"localhost:9092"},
        Topic:     "payments",
        Partition: 2,
})
defer reader.Close()

if err := reader.SetOffset(kafka.FirstOffset); err != nil {
        log.Fatalf("set offset: %v", err)
}

for {
        m, err := reader.ReadMessage(ctx)
        if err != nil {
                break
        }
        fmt.Println(m.Partition, m.Offset, string(m.Value))
}

ReaderConfig содержит Topic и Partition и не содержит GroupID — reader привязан ко второй партиции. SetOffset с константой FirstOffset переводит позицию на самый старый доступный оффсет: с учётом ретеншена это может быть не ноль, если старые сегменты уже удалены. Дальше обычный цикл ReadMessage: сообщения идут подряд по оффсетам, а m.Partition подтверждает, откуда они приехали.

kafka.Reader.SetOffset: три формы оффсета#

SetOffset принимает три вида значений, и все три полезны в разных задачах. Константа kafka.FirstOffset — минус два — переводит позицию на самый старый доступный оффсет партиции; с включённым ретеншеном «старый» значит «самый ранний из живых», а не ноль. Константа kafka.LastOffset — минус один — ставит reader на хвост: следующий ReadMessage заблокируется и вернёт первое новое сообщение, а не последнее лежащее; чтобы перечитать лежащее, нужен конкретный номер. Третья форма — число: SetOffset(42) позиционирует точно на оффсет 42, и следующий ReadMessage вернёт именно его. Метод работает на лету: позицию можно двигать между чтениями, двигаясь по истории туда-обратно, и каждое перемещение видно в логе: reader печатает «setting the offset of the kafka reader … from … to …», так что мухлеж с позицией сам себя выдаёт в логах. Два пограничных случая из исходников: после Close SetOffset возвращает io.ErrClosedPipe, а позиция меньше первого живого оффсета при чтении аккуратно доезжает до него — reader печатает в лог, сколько сообщений он пропустил прыжком. Историческая ремарка для тех, кто встретит старые примеры: в версии 0.2.0 значения минус единицы и минус двойки менялись местами, поэтому константы по именам — не стиль, а защита от копипасты с призраками.

Проверка: хвост партиции

В партиции лежат сообщения с оффсетами 0..99. Выполнен reader.SetOffset(kafka.LastOffset). Что вернёт следующий ReadMessage?

Offset, Lag и ReadLag: измерения без группы#

У groupless reader'а есть полный комплект измерений, который групповому недоступен. Offset() возвращает текущую позицию — абсолютный оффсет, на котором reader стоит. Lag() отдаёт кэшированный лаг: разницу между концом партиции и позицией, обновляемую по ReadLagInterval, который в конфиге можно отключить отрицательным значением. ReadLag(ctx) — свежий замер по сети: метод сходит к брокеру, узнаёт последний оффсет и считает лаг прямо сейчас; удобно для точечных проверок и метрик. Нюанс: при отрицательной позиции ReadLag возвращает ноль — «лаг неопределён, пока чтение не началось», и это стоит помнить, вызывая метод до первого чтения. Для группового режима всё это недоступно честно и по-разному: Offset и Lag возвращают минус единицу, а ReadLag — ошибку errNotAvailableWithGroup; лаг группы считается по агрегированным данным брокера, о чём говорит двадцать первый урок. Из полезных полей сообщения в этом режиме отметим HighWaterMark: каждое прочитанное сообщение несёт конец партиции на момент чтения — дешёвый способ оценить остаток без отдельного запроса. Все эти измерения превращают groupless reader в прибор: скрипт мониторинга, отладочный хвост, счётчик непрочитанного — всё это десять строк кода.

Хвост партиции с замером лага

GO
reader := kafka.NewReader(kafka.ReaderConfig{
        Brokers:   []string{"localhost:9092"},
        Topic:     "payments",
        Partition: 0,
})
defer reader.Close()

reader.SetOffset(kafka.LastOffset)

lag, err := reader.ReadLag(ctx)
if err != nil {
        log.Fatalf("lag: %v", err)
}
log.Printf("tailing, lag=%d", lag)

for {
        m, err := reader.ReadMessage(ctx)
        if err != nil {
                break
        }
        fmt.Printf("%d/%d %s
", m.Offset, m.HighWaterMark, m.Value)
}

Reader встаёт на хвост партиции и печатает каждое новое сообщение в формате оффсет/конец-партиции. ReadLag перед циклом даёт свежий замер по сети — для хвостового наблюдателя он почти всегда ноль, что и подтверждает работу. HighWaterMark в каждом сообщении позволяет не делать лишних запросов: разница между ним и текущим оффсетом — грубая оценка остатка на лету.

Проверка: SetOffset с GroupID

У reader задан GroupID. Что вернёт вызов reader.SetOffset(42)?

Какой оффсет у следующего сообщения?

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

// партиция содержит сообщения с оффсетами 0..9
reader.SetOffset(kafka.FirstOffset)
m1, _ := reader.ReadMessage(ctx) // оффсет 0
m2, _ := reader.ReadMessage(ctx) // оффсет 1

reader.SetOffset(5)
m3, _ := reader.ReadMessage(ctx)
fmt.Println(m3.Offset)

Сценарии без группы: реплей, аудит, ручная раздача#

Соберём список задач, где groupless reader — правильный инструмент, а не костыль. Реплей и backfill: перечитать диапазон оффсетов или день по времени после миграции или смены правил; группа для этого не годится — её позиция нельзя трогать без последствий. Аудит и отладка: пройти партицию с начала с чужой логикой, посмотреть живые события через хвост, сверить, что реально лежит в топике. Карантин: перечитать события из карантинного топика девятнадцатого урока с новыми правилами обработки — позиция карантинного джоба полностью под его контролем. Ручная раздача: когда по reader на партицию и своё распределение дают контроль, которого группа не даёт, — например, привязка партиций к мощностям инстансов. Мониторинг: ReadLag по каждой партиции в метрики — прибор из двадцать первого урока. Общая деталь всех сценариев — чекпоинт: утилита реплея обычно печатает последний обработанный оффсет в файл или переменную процесса запуска, чтобы после сбоя продолжить с него через SetOffset с числом, а не с начала; это тот же контракт, что и коммиты группы, только хранилище позиции — ваше. И обратный список: штатный поток событий с масштабированием — работа групп из седьмого урока; писать свой раздальщик партиций поверх groupless readers, чтобы избежать группы, — вот это уже костыль, который придётся поддерживать самому.

Сценарии groupless Reader одним списком#

  • Реплей: перечитать диапазон оффсетов или день по SetOffsetAt после миграции.
  • Отладка: догнать хвост через LastOffset и посмотреть живые события топика.
  • Аудит: пройти партицию с FirstOffset до конца с собственной логикой.
  • Карантин: повторно обработать события карантинного топика с новыми правилами.
  • Ручная раздача: по reader на партицию, когда нужны свои правила распределения.
  • Мониторинг: ReadLag по каждой партиции в метрики без агрегаторов.

Ниже Reader: DialLeader и сырой Conn#

Иногда нужен уровень ниже: kafka.DialLeader открывает прямое соединение с лидером конкретной партиции — DialLeader(ctx, "tcp", "localhost:9092", topic, partition) возвращает сырой *Conn. У него есть свои Read, ReadMessage, ReadBatch и своя навигация по оффсетам: Seek с Offset, а также ReadFirstOffset, ReadLastOffset и ReadOffsets — по сути, это транспорт без планировщика, пульсов и прочей обвязки Reader. Годится для точечных инструментов: быстро прочитать несколько сообщений, снять оффсеты, сделать разовую миграцию. Для сервисного кода Reader остаётся правильным выбором: он переживает переезды лидера, переконнекты и держит батчинг чтения — сырой Conn ничего этого не делает за вас. Практический ориентир: если утилита одноразовая и живёт секунды, DialLeader быстрее собрать и проще выбросить; если инструмент живёт в проде дольше минуты — берите Reader и получайте отказоустойчивость бесплатно.

Способы стартовать чтение одним столом#

МеханизмЧто делаетКогда брать
StartOffset в ReaderConfigСтартовая позиция нового reader, дефолт FirstOffsetЗадать старт один раз при создании
SetOffset(kafka.FirstOffset)Перевести позицию на самый старый живой оффсетПолный реплей партиции
SetOffset(kafka.LastOffset)Встать на хвост: ждать следующее новое сообщениеДогнать поток, отладка вживую
SetOffset(число)Перепрыгнуть на конкретный оффсетПовтор с известного места
SetOffsetAt(ctx, время)Найти оффсет по метке времениПеречитать вчерашний день
CommitMessages с GroupIDПозицию задаёт коммит группыШтатный поток событий

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

Соберите groupless reader с перемоткой

Допишите партицию в конфиге и перемотку на начало.

reader := kafka.NewReader(kafka.ReaderConfig{ Brokers: []string{"localhost:9092"}, Topic: "payments", : 2, }) defer reader.Close() if err := reader.(kafka.); err != nil { log.Fatalf("rewind: %v", err) }

Соберите хвостовой наблюдатель

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

Строки

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

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

Карточки: Reader API и оффсеты

Термин

Groupless Reader

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

ReaderConfig с Partition и без GroupID: один reader на партицию, позиция под полным контролем кода.

1 / 6

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

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

Вывод

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

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

  • Reader без группы: Partition вместо GroupID, один reader — одна партиция.
  • NewReader паникует, если заданы и Partition, и GroupID: либо группа, либо прямое чтение.
  • SetOffset принимает FirstOffset, LastOffset или конкретный оффсет и работает на лету между чтениями.
  • LastOffset ставит reader на хвост: следующий ReadMessage ждёт новое сообщение, а не возвращает лежащее.
  • SetOffsetAt ищет оффсет по времени: реплей дня без ручного подбора числа.
  • Offset, Lag и ReadLag — полный комплект измерений только без группы: с ней Offset и Lag дают -1.
  • Lag кэшируется и обновляется раз в ReadLagInterval; свежий замер — только ReadLag с походом к брокеру.
  • Реплей, аудит, карантин и ручная раздача — работа groupless Reader; штатный поток живёт в группах.

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

Возьмите топик payments из прошлых уроков и напишите утилиту реплея: SetOffsetAt на вчерашний момент, печать партиция/оффсет/значение до конца, счётчик событий. Затем хвостовой режим: SetOffset с LastOffset, ReadLag до и после — убедитесь, что лаг на хвосте около нуля, и что утилита ждёт новые события. Третье упражнение: создайте reader с GroupID и попытайтесь вызвать SetOffset — опишите полученную ошибку словами и объясните, почему API так устроен. Финал: посчитайте ReadLag по всем партициям топика groupless-ридерами и сверьте сумму с тем, что вы узнаете в двадцать первом уроке.

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

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

Группа тоже читает с оффсетов — зачем отдельный режим?

Группа читает с закоммиченной позиции, и её позиция — общая собственность членов: перематывать её для реплея значит ломать поток. Штатного API перемотки группы в kafka-go нет: SetOffset с GroupID возвращает ошибку. Реплей делают groupless reader'ом или временной группой с новым GroupID и StartOffset — второй способ удобен, когда перечитывать надо многими инстансами. Третий, тонкий вариант: существующую группу можно сдвинуть коммитом — утилита с тем же GroupID коммитит желаемый оффсет, и группа продолжит с него; операция штатная, но с последствиями для всех членов, поэтому делается на паузе или по регламенту.

Сколько groupless readers можно держать в одном процессе?

По одному на партицию — и это разумный потолок: reader привязан к партиции, и два reader на одну партицию дадут два независимых потока одинаковых сообщений. Параллелизм масштабируется числом reader'ов, каждый со своим циклом обработки. Помните, что позицию при остановке придётся хранить самим: groupless reader не коммитит оффсеты в брокер.

Что вернёт ReadMessage, когда партиция дочитана?

Заблокируется в ожидании новых сообщений — чтение партиции по определению бесконечно, конец сейчас означает «пока конец». Завершение цикла организуется контекстом: отмена прервёт ReadMessage с ошибкой, и утилита сможет завершиться аккуратно. Если нужно ограничить объём — сравнивайте m.Offset с границей или считайте сообщения и выходите по счётчику. Отдельный пограничный случай — старт с LastOffset на пустой партиции: позиция резолвится в ноль, и первое же сообщение станет первым прочитанным.

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

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

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

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

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

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

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