---
title: "Знакомство: зачем шина событий"
description: "Зачем kafka Go-сервису: лог против очереди, чем шина событий отличается от RabbitMQ, роли топиков и первый взгляд на клиент kafka-go с примерами кода."
course: "Kafka в Go: продюсеры и консьюмеры на kafka-go"
url: "https://golangmaster.ru/courses/kafka-go/lessons/01-znakomstvo-kafka/"
published: "2026-09-19"
updated: "2026-09-29"
minutes: 16
---

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

*Курс «Kafka в Go: продюсеры и консьюмеры на kafka-go», урок 1 из 25. ~16 минут. Обновлено 29.09.2026. Источник: https://golangmaster.ru/courses/kafka-go/lessons/01-znakomstvo-kafka/*

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

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

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

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

> **Событие — не задача.** Шина событий и очередь задач решают разные дела. Событие — факт «заказ 42 создан»: его читают все, кому нужно. Задача — просьба «отправь письмо»: её выполняет ровно один воркер, и без подтверждения она не исчезнет. Kafka сильна в событиях, RabbitMQ — в задачах с подтверждениями и маршрутизацией; простые фоновые задачи закрывает даже asynq на Redis. Если в системе только задачи, Kafka не окупится — честно считайте, прежде чем заводить ещё один сервис.

### Очередь против лога: чем 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 | лежит в логе до ретеншена | да — оффсеты сдвигаются назад | события, потоки между сервисами |

> **Лог — не очередь: диск не бездонный.** Типичная ошибка при переезде с RabbitMQ: команда переносит поток и оставляет настройки хранения по умолчанию, предполагая, что «прочитанное само исчезнет». Оно не исчезает: топик копит события неделю по умолчанию, а с широкой аудиторией — десятки гигабайт в день. Через месяц диск кончается, брокер отказывается принимать записи, и продюсеры падают по всему контуру одновременно. Ретеншен и его цена — отдельная тема курса; здесь запомните главное: лог хранит всё, что вы в него положили. Симптом узнаваем: мониторинг диска краснеет раньше всех графиков, а продюсерские ошибки появляются одновременно на всех сервисах — верный признак, что хранение в Kafka планировали как очередь.

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

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

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

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

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

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

- ✅ Останется в логе до истечения срока хранения
- Удалится немедленно, как в RabbitMQ после ack
- Переместится в архивный топик
- Перезапишется следующим сообщением

Чтение в Kafka ничего не удаляет: сообщение остаётся в логе, пока не истечёт срок хранения или топик не переполнится по размеру. Именно поэтому другая группа может прочитать то же событие позже, а продюсеру не нужно «раздавать» копии.

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

**Вопрос:** Что Kafka, в отличие от RabbitMQ, не умеет из коробки?

- Хранить события неделями и переживать рестарты
- ✅ Маршрутизировать сообщение по правилам в несколько очередей
- Держать порядок сообщений с одинаковым ключом
- Обслуживать много независимых групп читателей

Кafka не имеет exchange-механики: продюсер сам решает, в какой топик и партицию писать, а читатели подписываются на топики целиком. Правила маршрутизации вроде direct и topic-обменников — территория RabbitMQ; в Kafka её эмулируют отдельными топиками под каждую аудиторию.

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

```go
// Группа 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)
```

**Что выведет программа?**

- ✅ Самое старое сообщение лога — новый оффсет по умолчанию начинается с начала
- Первое непрочитанное для billing — оффсеты общие для всех групп
- Ошибку: топик уже занят другой группой
- Оффсеты начнутся с текущего конца лога

Оффсеты привязаны к группе: у analytics их ещё нет, и kafka-go по умолчанию начинает с FirstOffset — самого старого живого сообщения лога. Именно так новый сервис восстанавливает историю; управлять стартом оффсета можно явными настройками Reader, что разберём в уроках про группы и Reader API.

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

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

```go
writer := kafka.___(kafka.WriterConfig{
        Brokers: []___{"localhost:9092"},
        Topic:   "orders",
})

err := writer.___(ctx, kafka.Message{
        Key:   []byte("order-42"),
        Value: []byte("created"),
})
```

Подсказка: Конструктор называется NewWriter, адреса — это срез строк, а отправляет метод WriteMessages.

Правильные ответы в пропусках: `NewWriter`, `string`, `WriteMessages`

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

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

Правильный порядок строк:

1. `ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)`
2. `defer cancel()`
3. `writer := kafka.NewWriter(kafka.WriterConfig{Brokers: []string{"localhost:9092"}, Topic: "orders"})`
4. `defer writer.Close()`
5. `msg := kafka.Message{Key: []byte("order-42"), Value: []byte("created")}`
6. `err := writer.WriteMessages(ctx, msg)`
7. `fmt.Println("отправлено:", err == nil)`

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

- **Шина событий** — Модель интеграции, при которой сервисы публикуют факты в общий журнал, а не дёргают друг друга по HTTP. Kafka — её самая известная реализация.
- **Лог (append-only)** — Хранилище, в которое записи только дописываются в конец и не изменяются задним числом. Сообщения остаются до истечения срока хранения.
- **Оффсет** — Номер позиции сообщения в партиции. Потребитель двигает свой оффсет сам — чтение и подтверждение здесь раздельные шаги.
- **Партиция** — Осколок топика, дописываемый параллельно другим. Порядок гарантирован только внутри неё, о деталях — следующий урок.
- **Группа консьюмеров** — Набор читателей с общим GroupID, который делит партиции между собой. Разные группы читают один топик независимо.
- **kafka-go** — Клиент Kafka для Go от segmentio: чистый Go без cgo, API из Writer, Reader и Admin-клиента. Путь импорта — github.com/segmentio/kafka-go.

Карта собрана: Kafka — журнал событий на диске, из которого многие читают независимо, а kafka-go — короткий путь до него из Go через Writer и Reader. Дальше по порядку: в [уроке про топики, партиции и оффсеты](/courses/kafka-go/lessons/02-koncepty-brokera/) разберём, как брокер разрезает поток и почему порядок живёт внутри партиции; в [уроке установки](/courses/kafka-go/lessons/03-ustanovka-brokera/) поднимем брокер в Docker одной командой и создадим первый топик. Границы выбора мы с RabbitMQ уже наметили — когда понадобится маршрутизация задач с подтверждениями, [курс по RabbitMQ](/courses/rabbitmq/) останется правильным инструментом. Домашнее задание ниже — про площадку: без живого брокера уроки с четвёртого превратятся в чтение. Если сомневаетесь, стоит ли вам всё это — перечитайте спорный блок выше и посчитайте своих потребителей: числа обычно отвечают быстрее статей.

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

- 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 важнее — скилл переноса на другой клиент останется, потому что концепции одни.
