Содержание урока · 10 разделов
- Зачем kafka: четыре роли шины событий
- Очередь против лога: чем Kafka отличается от RabbitMQ
- Что лог берёт на себя
- Kafka в Go: клиент kafka-go и его соседи
- Лог или очередь: сравнение на задачах
- Кейс: события заказа, которые нельзя потерять
- Спорное мнение: не каждому сервису нужна шина
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
Начнём с ситуации, в которой шина событий появляется не из любопытства, а из боли. Интернет-магазин: заказ, оплата, письмо, аналитика — пока сервисов два, всё живёт на HTTP: checkout дёргает платежи, платежи дёргают почту, почта дёргает аналитику. Каждый участник обязан знать адрес каждого следующего, каждый вызов замедляет предыдущий, а отказ SMTP останавливает оформление заказа, хотя письмо нужно через минуту, а не прямо сейчас. Добавьте третий и четвёртый подписчика на событие «заказ создан» — и checkout обрастает вызовами, о которых он знать не должен. Классическое лечение — очередь: положи задачу и не жди. Но у очереди есть предел: сообщение уходит одному потребителю и после подтверждения исчезает навсегда. Аналитике и биллингу нужны одни и те же события, а повторить вчерашний поток для отладки невозможно — данных больше нет. Kafka выходит из другой парадигмы: она не раздаёт сообщения, а хранит их как журнал, из которого каждый читает сам. Полный план из двадцати пяти уроков лежит на странице курса; здесь — карта местности без лишней теории. Дальше — по нарастающей: четыре роли шины, разбор отличий от очереди, клиент и кейс, после которого спор обычно заканчивается.
Зачем kafka: четыре роли шины событий#
Вопрос «зачем kafka» честнее отвечать ролями, а не определениями. Первая роль — журнал интеграции: сервисы обмениваются событиями через общий топик, не зная друг о друге; checkout публикует «заказ создан» и не интересуется, кто его слушает. Вторая — буфер на пики: продюсер пишет в лог со скоростью сети, а потребители разгребают в своём темпе; всплеск в пятницу вечером копится в топике, а не роняет вызывающие цепочки. Третья — история для повторного чтения: новый сервис, подключившийся через полгода после запуска потока, вычитывает события с самого начала и восстанавливает состояние без ручной синхронизации из базы. Четвёртая — потоковая обработка: аналитика, агрегаты и фиды собираются из того же журнала, который уже обслуживает продакшен-сервисы, без второго канала доставки. Заметьте общий знаменатель: везде событие — это факт, записанный на диск, а не задача, которую надо раздать и дождаться. Пока ваши данные исчезают после прочтения, все четыре роли недоступны в принципе. Отсюда и порядок изложения курса: сначала механика хранения и адресации, потом продюсер с ключами, потом читатели — на каждом шаге вы будете возвращаться к этим четырём ролям и узнавать их в коде.
Шина событий и очередь задач решают разные дела. Событие — факт «заказ 42 создан»: его читают все, кому нужно. Задача — просьба «отправь письмо»: её выполняет ровно один воркер, и без подтверждения она не исчезнет. Kafka сильна в событиях, RabbitMQ — в задачах с подтверждениями и маршрутизацией; простые фоновые задачи закрывает даже asynq на Redis. Если в системе только задачи, Kafka не окупится — честно считайте, прежде чем заводить ещё один сервис.
Очередь против лога: чем Kafka отличается от RabbitMQ#
Главное различие — судьба сообщения после чтения. В RabbitMQ сообщение маршрутизируется обменником в очередь, доставляется потребителю и после подтверждения удаляется: очередь — это буфер, который обязан опустошаться. В Kafka сообщение дописывается в конец топика и остаётся там до истечения срока хранения — по умолчанию примерно неделю, срок настраивается на топик. Потребитель не забирает сообщение, а читает его по оффсету — номеру позиции в логе — и двигает оффсет сам, отдельно от чтения. Отсюда три практических следствия. Первое: несколько независимых групп потребителей читают один топик в своём ритме, не мешая друг другу, — аналитика не конкурирует с биллингом. Второе: перечитать старые сообщения можно в любой момент — оффсеты крутятся назад, и поток проигрывается заново. Третье: у потребителя появляется измеримое отставание — лаг между последним сообщением и текущим оффсетом, который становится главной метрикой мониторинга. Платит Kafka за это отсутствием маршрутизации: никакого exchange с правилами — продюсер сам выбирает топик и партицию, дальше никаких развилок. Это и есть короткая шпаргалка: если вам нужен один получатель с подтверждением — очередь; если один факт для многих читателей с историей — лог.
Первый взгляд: kafka-go в паре десятков строк
GOpackage 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 | лежит в логе до ретеншена | да — оффсеты сдвигаются назад | события, потоки между сервисами |
Типичная ошибка при переезде с 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 — журнал событий на диске, из которого многие читают независимо, а 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 заметно сложнее RabbitMQ — стоит ли начинать с неё?
Для событий между сервисами сложность окупается: хранение, независимые группы и повторное чтение в RabbitMQ либо недоступны, либо эмулируются с трудом. Начать с RabbitMQ, чтобы почувствовать брокеры вообще, — нормальный путь: концепции очередей переносятся, а разбор выбора под задачу есть в конце этого курса.
Сколько ресурсов нужно локальному брокеру для учёбы?
JVM Kafka по умолчанию просит около гигабайта кучи, и для одного топика с тремя партициями этого с запасом хватает; лимит настраивается переменной KAFKA_HEAP_OPTS. Диск занимает не процесс, а ваш трафик: при учебных нагрузках счёт идёт на десятки мегабайт.
Почему курс на kafka-go, а не на sarama или franz-go?
kafka-go даёт самый короткий путь от нуля до рабочего продюсера и консьюмера: чистый Go без cgo и API из двух интерфейсов. Sarama старше и функциональнее, franz-go быстрее на больших потоках, но для обучения и типовых сервисов простота kafka-go важнее — скилл переноса на другой клиент останется, потому что концепции одни.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
Следующая ступень учебного пути
Ebitengine: 2D-игры на Go
Курс Ebitengine для Go: игровой цикл Update/Draw, спрайты и анимации, ввод, коллизии, тайлмапы, камера, звук, сцены и сборка на платформы. 25 уроков.
- Понимать игровой цикл Update/Draw и Layout
- Рисовать спрайты, атласы и примитивы
- Обрабатывать ввод: клавиатура, мышь, тач
- Строить движение, коллизии и анимации