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

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

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

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

Знакомство: зачем шина событий

Зачем kafka Go-сервису, когда уже есть RabbitMQ, Redis и горутины с каналами? Ответ в этом уроке — не про моду, а про механику: Kafka хранит события как журнал на диске, из которого несколько команд читают независимо и в своём темпе. Разберём, чем шина событий отличается от очереди сообщений, какие задачи ложатся на лог, а какие лучше оставить очереди, и соберём первый взгляд на клиент kafka-go.

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

Начнём с ситуации, в которой шина событий появляется не из любопытства, а из боли. Интернет-магазин: заказ, оплата, письмо, аналитика — пока сервисов два, всё живёт на HTTP: checkout дёргает платежи, платежи дёргают почту, почта дёргает аналитику. Каждый участник обязан знать адрес каждого следующего, каждый вызов замедляет предыдущий, а отказ SMTP останавливает оформление заказа, хотя письмо нужно через минуту, а не прямо сейчас. Добавьте третий и четвёртый подписчика на событие «заказ создан» — и checkout обрастает вызовами, о которых он знать не должен. Классическое лечение — очередь: положи задачу и не жди. Но у очереди есть предел: сообщение уходит одному потребителю и после подтверждения исчезает навсегда. Аналитике и биллингу нужны одни и те же события, а повторить вчерашний поток для отладки невозможно — данных больше нет. Kafka выходит из другой парадигмы: она не раздаёт сообщения, а хранит их как журнал, из которого каждый читает сам. Полный план из двадцати пяти уроков лежит на странице курса; здесь — карта местности без лишней теории. Дальше — по нарастающей: четыре роли шины, разбор отличий от очереди, клиент и кейс, после которого спор обычно заканчивается.

Зачем kafka: четыре роли шины событий#

Вопрос «зачем kafka» честнее отвечать ролями, а не определениями. Первая роль — журнал интеграции: сервисы обмениваются событиями через общий топик, не зная друг о друге; checkout публикует «заказ создан» и не интересуется, кто его слушает. Вторая — буфер на пики: продюсер пишет в лог со скоростью сети, а потребители разгребают в своём темпе; всплеск в пятницу вечером копится в топике, а не роняет вызывающие цепочки. Третья — история для повторного чтения: новый сервис, подключившийся через полгода после запуска потока, вычитывает события с самого начала и восстанавливает состояние без ручной синхронизации из базы. Четвёртая — потоковая обработка: аналитика, агрегаты и фиды собираются из того же журнала, который уже обслуживает продакшен-сервисы, без второго канала доставки. Заметьте общий знаменатель: везде событие — это факт, записанный на диск, а не задача, которую надо раздать и дождаться. Пока ваши данные исчезают после прочтения, все четыре роли недоступны в принципе. Отсюда и порядок изложения курса: сначала механика хранения и адресации, потом продюсер с ключами, потом читатели — на каждом шаге вы будете возвращаться к этим четырём ролям и узнавать их в коде.

Очередь против лога: чем Kafka отличается от RabbitMQ#

Главное различие — судьба сообщения после чтения. В RabbitMQ сообщение маршрутизируется обменником в очередь, доставляется потребителю и после подтверждения удаляется: очередь — это буфер, который обязан опустошаться. В Kafka сообщение дописывается в конец топика и остаётся там до истечения срока хранения — по умолчанию примерно неделю, срок настраивается на топик. Потребитель не забирает сообщение, а читает его по оффсету — номеру позиции в логе — и двигает оффсет сам, отдельно от чтения. Отсюда три практических следствия. Первое: несколько независимых групп потребителей читают один топик в своём ритме, не мешая друг другу, — аналитика не конкурирует с биллингом. Второе: перечитать старые сообщения можно в любой момент — оффсеты крутятся назад, и поток проигрывается заново. Третье: у потребителя появляется измеримое отставание — лаг между последним сообщением и текущим оффсетом, который становится главной метрикой мониторинга. Платит Kafka за это отсутствием маршрутизации: никакого exchange с правилами — продюсер сам выбирает топик и партицию, дальше никаких развилок. Это и есть короткая шпаргалка: если вам нужен один получатель с подтверждением — очередь; если один факт для многих читателей с историей — лог.

Первый взгляд: kafka-go в паре десятков строк

GO
package main

import (
        "context"
        "fmt"
        "time"

        "github.com/segmentio/kafka-go"
)

func main() {
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()

        writer := kafka.NewWriter(kafka.WriterConfig{
                Brokers: []string{"localhost:9092"},
                Topic:   "orders",
        })
        defer writer.Close()

        err := writer.WriteMessages(ctx, kafka.Message{
                Key:   []byte("order-42"),
                Value: []byte("created"),
        })
        fmt.Println("отправлено:", err)

        reader := kafka.NewReader(kafka.ReaderConfig{
                Brokers: []string{"localhost:9092"},
                GroupID: "billing",
                Topic:   "orders",
        })
        defer reader.Close()

        m, err := reader.ReadMessage(ctx)
        fmt.Println(string(m.Key), string(m.Value), m.Partition, m.Offset)
}

Телепорт вперёд, чтобы снять магию: kafka.NewWriter с адресом брокера и топиком отправляет сообщение через WriteMessages, kafka.NewReader с GroupID читает его обратно методом ReadMessage. Сообщение — это байты: Key влияет на партицию, Value — полезная нагрузка, а ReadMessage возвращает Topic, Partition и Offset, о которых дальше целый урок. Все детали API разберём с четвёртого урока; пока важно, что весь клиент — два конструктора и два метода.

Что лог берёт на себя#

  • Хранение: события лежат на диске по сроку или размеру, а не до первого подтверждения.
  • Оффсеты: позиция каждого потребителя — его личное дело, брокер не раздаёт сообщения.
  • Партиции: топик режется на осколки, которые пишутся и читаются параллельно.
  • Репликация: каждая партиция копируется на несколько брокеров и переживает падение одного из них.
  • Повторное чтение: сдвинул оффсет назад — проиграл историю заново.
  • Несколько групп: биллинг, аналитика и новый сервис читают один поток независимо.

Меню: три события одного заказа

GO
// ключ один — партиция тоже: порядок сохранится
writer.WriteMessages(ctx,
        kafka.Message{Key: []byte("order-42"), Value: []byte("created")},
)
writer.WriteMessages(ctx,
        kafka.Message{Key: []byte("order-42"), Value: []byte("paid")},
)
writer.WriteMessages(ctx,
        kafka.Message{Key: []byte("order-42"), Value: []byte("shipped")},
)

Меню без импортов: три события заказа с одним ключом order-42. Hash-балансировщик положит их в одну партицию, и читатель увидит строгую последовательность created, paid, shipped — зародыш того, что на пятом уроке станет продюсером с ключами. Value здесь строки для наглядности: в боевом коде на их месте будет сериализованная структура из одиннадцатого урока.

Kafka в Go: клиент kafka-go и его соседи#

Запрос kafka golang в поиске приводит к трём именам, и курс построен на kafka-go — библиотеке от компании segmentio. Её главный козырь — чистый Go: без cgo, без librdkafka, без крестовой компиляции, обычный go build собирает статичный бинарник, который деплоится куда угодно. API самый простой в экосистеме: Writer для записи, Reader для чтения, Admin-клиент для управления — интерфейсы, которые читаются слева направо. Официального клиента Apache Kafka для Go не существует в природе, поэтому выбор — между реализациями: sarama старше всех и умеет больше всего, но его API настолько объёмный, что половина туториалов начинается с боли; franz-go моложе и агрессивнее по производительности, хорош для высоконагруженных потоков; confluent-kafka-go — обёртка над C-библиотекой librdkafka, быстрая, но тянет за собой cgo и усложняет сборку в Docker с musl. Для обучения, типовых сервисов и средних нагрузок kafka-go закрывает всё: на ней и пройдём весь курс, а где у альтернатив потолок — отдельно в конце. Установка библиотеки и локального брокера — третий урок, туда и держим путь. Ничего страшного в этом выборе нет: API, изученный здесь, переносится между клиентами почти один к одному — Writer, Reader, метаданные и ошибки с кодами везде выглядят похоже, меняется только обвязка.

Лог или очередь: сравнение на задачах#

ПодходСудьба после чтенияПеречитать историюТиповое применение
HTTP-вызов между сервисамине хранится нигденетбыстрый синхронный диалог
Горутина с каналомканал умирает с процессомнетфон внутри одного процесса
RabbitMQудаляется после подтверждениянетзадачи, письма, интеграции
Kafkaлежит в логе до ретеншенада — оффсеты сдвигаются назадсобытия, потоки между сервисами

Кейс: события заказа, которые нельзя потерять#

Кейс одной платформы, после которого шина перестала быть дискуссией. Платежи и заказы жили в Postgres, а об интеграции оповещали себя же: после коммита транзакции код дёргал HTTP-эндпоинты биллинга и аналитики. При падении биллинга события терялись молча — письма со статусом «оплата не дошла» собирались раз в неделю. Попытка чинить ретраями добавила дубли: аналитика считала один заказ дважды. Переезд на Kafka решает обе проблемы механикой: событие пишется в лог один раз, до упора — с подтверждением брокера; биллинг и аналитика читают независимо, каждый со своим оффсетом; упавший биллинг после рестарта продолжает с того места, где остановился, а не с того, что не забрал. Через полгода подключили антифрод: он вычитал историю с начала и восстановил картину без выгрузок из базы. Ключевой эффект не в скорости — в том, что потеря события перестала быть вопросом вежливости HTTP-эндпоинта. Отдельно заметьте, что шина здесь не заменила базу: Postgres остался источником правды, Kafka взяла на себя именно доставку событий — этот раздел ответственности проходит через весь курс.

Спорное мнение: не каждому сервису нужна шина#

Мнение для архитектурного созвона: пока сервис один, а потребителей события ноль или один, Kafka — лишняя зависимость. Брокер — это не библиотека, а демон с диском, JVM, мониторингом и местом в дежурном чате; монолит вместо него живёт с таблицей событий в Postgres и воркером на горутинах, и это честное решение на годы. Условие переезда конкретное: появились два и больше независимых потребителей одного потока, либо потеря события стоит денег, либо нужно повторное чтение истории. Пока условия нет, добавление Kafka — это минус один вечер на деплой и минус одна нить в дежурстве, а выигрывает строчка в резюме. Сравнение с очередями задач мы уже делали в курсе по RabbitMQ — там же про ack, dead letter и маршрутизацию, которые Kafka из коробки не даёт. Одна оговорка о формате этого курса: песочница сайта не умеет сеть и TCP-сокеты, поэтому kafka-код запускается локально против брокера в Docker, а интерактивы песочницы показывают тематические программы на чистом Go.

Проверка: судьба сообщения

Консьюмер прочитал сообщение и подтвердил оффсет. Что произойдёт с сообщением в Kafka?

Проверка: чего у Kafka нет

Что Kafka, в отличие от RabbitMQ, не умеет из коробки?

Что прочитает новая группа?

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

// Группа billing давно читает топик orders
// и хранит свой оффсет в брокере.

reader := kafka.NewReader(kafka.ReaderConfig{
        Brokers: []string{"localhost:9092"},
        Topic:   "orders",
        GroupID: "analytics", // группа появилась только что
})

m, err := reader.ReadMessage(ctx)
fmt.Println(m.Offset, err)

Соберите отправку события

Допишите конструктор Writer, тип адресов брокера и вызов отправки.

writer := kafka.(kafka.WriterConfig{ Brokers: []{"localhost:9092"}, Topic: "orders", }) err := writer.(ctx, kafka.Message{ Key: []byte("order-42"), Value: []byte("created"), })

Соберите скелет продюсера

Расставьте строки в порядке работы программы: контекст, Writer, его закрытие, сообщение, отправка, вывод.

Строки

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

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

Карточки: знакомство с Kafka

Термин

Шина событий

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

Модель интеграции, при которой сервисы публикуют факты в общий журнал, а не дёргают друг друга по HTTP. Kafka — её самая известная реализация.

1 / 6

Карта собрана: Kafka — журнал событий на диске, из которого многие читают независимо, а kafka-go — короткий путь до него из Go через Writer и Reader. Дальше по порядку: в уроке про топики, партиции и оффсеты разберём, как брокер разрезает поток и почему порядок живёт внутри партиции; в уроке установки поднимем брокер в Docker одной командой и создадим первый топик. Границы выбора мы с RabbitMQ уже наметили — когда понадобится маршрутизация задач с подтверждениями, курс по RabbitMQ останется правильным инструментом. Домашнее задание ниже — про площадку: без живого брокера уроки с четвёртого превратятся в чтение. Если сомневаетесь, стоит ли вам всё это — перечитайте спорный блок выше и посчитайте своих потребителей: числа обычно отвечают быстрее статей.

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

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

Вывод

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

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

  • Kafka — журнал событий: сообщения дописываются в конец и лежат до истечения ретеншена, а не удаляются после чтения.
  • Оффсеты — позиции читателей: каждая группа двигает их независимо, историю можно проиграть заново.
  • Шина событий — про факты для многих читателей; задачи с подтверждениями и маршрутизацией — про очередь и RabbitMQ.
  • Партиции дают параллелизм и порядок внутри себя — фундамент, на котором стоит весь курс.
  • kafka-go — чистый Go без cgo: Writer, Reader и Admin-клиент как три простых интерфейса.
  • Лог хранит всё положенное: ретеншен надо планировать, иначе диск кончится в самый неподходящий момент.
  • Песочница не умеет сеть: kafka-код — локальный Go и брокер в Docker, интерактивы — тематические программы на чистом Go.

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

Подготовьте площадку до следующего урока: поставьте Go с go.dev и Docker, затем запустите брокер одной командой — docker run --name go-kafka -p 9092:9092 -e KAFKA_CFG_NODE_ID=0 -e KAFKA_CFG_PROCESS_ROLES=controller,broker -e KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 -e KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 -e KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER -e KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@localhost:9093 -d bitnami/kafka:3.7. Это тот же конфиг, что и в docker-compose.yml третьего урока: префикс KAFKA_CFG_ — способ образа bitnami задавать настройки server.properties. Проверьте контейнер через docker ps, затем напишите на бумаге три события вашего домена и для каждого придумайте ключ: что должно жить в одном порядке.

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

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

Kafka заметно сложнее RabbitMQ — стоит ли начинать с неё?

Для событий между сервисами сложность окупается: хранение, независимые группы и повторное чтение в RabbitMQ либо недоступны, либо эмулируются с трудом. Начать с RabbitMQ, чтобы почувствовать брокеры вообще, — нормальный путь: концепции очередей переносятся, а разбор выбора под задачу есть в конце этого курса.

Сколько ресурсов нужно локальному брокеру для учёбы?

JVM Kafka по умолчанию просит около гигабайта кучи, и для одного топика с тремя партициями этого с запасом хватает; лимит настраивается переменной KAFKA_HEAP_OPTS. Диск занимает не процесс, а ваш трафик: при учебных нагрузках счёт идёт на десятки мегабайт.

Почему курс на kafka-go, а не на sarama или franz-go?

kafka-go даёт самый короткий путь от нуля до рабочего продюсера и консьюмера: чистый Go без cgo и API из двух интерфейсов. Sarama старше и функциональнее, franz-go быстрее на больших потоках, но для обучения и типовых сервисов простота kafka-go важнее — скилл переноса на другой клиент останется, потому что концепции одни.

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

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

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

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

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

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

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