> Для чего нужен брокер сообщений Kafka (Go)

Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы

Компании: Wildberries

Стек: Go

> Пример ответа

Короткий ответ

Apache Kafka - это распределённая платформа потоковой передачи событий, которая решает три задачи: буферизация данных между продюсерами и консьюмерами, надёжная доставка сообщений с гарантиями durability, и масштабируемая обработка потоков в реальном времени. В Go-сервисах Kafka используется для асинхронной интеграции микросервисов, event sourcing, CDC, метрик и логов. Ключевые свойства - партиционирование, репликация, retention и consumer groups, которые позволяют строить отказоустойчивые конвейеры данных.

Подробное объяснение

Kafka - это не классическая очередь сообщений, а распределённый commit log. Основные компоненты:

  • Topic - именованный поток записей, разбитый на партиции.
  • Partition - упорядоченная последовательность сообщений, единица параллелизма.
  • Offset - позиция сообщения внутри партиции.
  • Consumer group - группа консьюмеров, где каждая партиция обрабатывается только одним консьюмером группы.

Зачем нужен Kafka в Go-проектах:

  1. Развязывание сервисов - продюсер не ждёт ответа консьюмера, что снижает latency и повышает отказоустойчивость.
  2. Буферизация пиковых нагрузок - Kafka выдерживает всплески, сглаживая нагрузку на downstream-сервисы.
  3. Гарантии доставки - at-least-once по умолчанию, exactly-once через транзакции и idempotent producer.
  4. Повторная обработка - консьюмер может перечитать сообщения с произвольного offset, что критично для дебага и восстановления.
  5. Event sourcing - Kafka хранит полную историю событий, позволяя восстанавливать состояние системы.
  6. Интеграция с экосистемой - Kafka Connect, Kafka Streams, ksqlDB для ETL и потоковой аналитики.

В Go типичный сценарий: HTTP-сервис пишет события в Kafka, background worker (consumer) обрабатывает их и обновляет БД или вызывает внешние API. Это даёт асинхронность и горизонтальное масштабирование: увеличиваешь партиции и добавляешь консьюмеров.

На практике

Для Go-разработчика важно понимать:

  • Выбор клиента: segmentio/kafka-go - чистая реализация, confluent-kafka-go - обёртка над librdkafka (выше производительность, но CGO).
  • Конфигурация продюсера: acks=all, retries>0, enable.idempotence=true для надёжности.
  • Конфигурация консьюмера: auto.offset.reset=earliest для новых групп, ручной commit offset для контроля обработки.
  • Обработка ошибок: retry с backoff, DLQ (dead letter queue) для невалидных сообщений.
  • Мониторинг: метрики lag consumer group, throughput, error rate через Prometheus.

Типичный пайплайн: REST API → Kafka topic orders → consumer валидирует и пишет в Postgres → другой consumer отправляет email. При сбое consumer'а сообщения не теряются, а ждут в Kafka.

Пример кода

Продюсер на segmentio/kafka-go:

GO
package main
import (
"context"
"log"
"time"
"github.com/segmentio/kafka-go"
)
func main() {
writer := &kafka.Writer{
Addr: kafka.TCP("localhost:9092"),
Topic: "orders",
Balancer: &kafka.Hash{},
RequiredAcks: kafka.RequireAll,
Async: false,
}
defer writer.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := writer.WriteMessages(ctx, kafka.Message{
Key: []byte("user-123"),
Value: []byte(`{"order_id":"123","amount":100}`),
})
if err != nil {
log.Fatal("failed to write message:", err)
}
}

Консьюмер с ручным commit:

GO
package main
import (
"context"
"log"
"time"
"github.com/segmentio/kafka-go"
)
func main() {
reader := kafka.NewReader(kafka.ReaderConfig{
Brokers: []string{"localhost:9092"},
Topic: "orders",
GroupID: "order-processor",
MinBytes: 10e3,
MaxBytes: 10e6,
CommitInterval: time.Second,
})
defer reader.Close()
ctx := context.Background()
for {
msg, err := reader.FetchMessage(ctx)
if err != nil {
log.Printf("fetch error: %v", err)
continue
}
// обработка сообщения
processOrder(msg.Value)
// коммитим offset только после успешной обработки
if err := reader.CommitMessages(ctx, msg); err != nil {
log.Printf("commit error: %v", err)
}
}
}
func processOrder(data []byte) {
// валидация и запись в БД
}

Как отвечать на собеседовании

Начни с краткого определения: распределённый commit log для потоковых данных. Затем перечисли задачи: асинхронная интеграция, буферизация, event sourcing. Подчеркни отличие от RabbitMQ: Kafka хранит историю, поддерживает replay, масштабируется через партиции.

Упомяни гарантии: at-least-once, exactly-once через транзакции. Расскажи про consumer groups и партиционирование - это ключевое для понимания параллелизма.

Приведи пример из практики: как ты использовал Kafka в Go для развязки сервисов или обработки событий. Если спросят про альтернативы - сравни с RabbitMQ, NATS, Google Pub/Sub.

Заверши trade-off: Kafka сложнее в эксплуатации (ZooKeeper/KRaft, ребалансировка), но даёт высокую пропускную способность и надёжность.

Что проверяет интервьюер

  • Понимание модели данных Kafka: topic, partition, offset, consumer group.
  • Умение объяснить гарантии доставки и их реализацию.
  • Знание практических аспектов: конфигурация, обработка ошибок, мониторинг lag.
  • Опыт работы с Go-клиентами и типичные проблемы (rebalance, commit semantics).
  • Способность выбирать между Kafka и другими брокерами под задачу.

Типичные ошибки

  • Путать Kafka с обычной очередью: говорить про удаление сообщений после чтения.
  • Не упоминать партиционирование и его влияние на порядок сообщений.
  • Заявлять exactly-once без оговорки про транзакции и идемпотентность.
  • Игнорировать consumer group rebalance и его последствия.
  • Не различать commit offset и auto-commit - это частая причина потери данных.
  • Говорить, что Kafka "быстрее RabbitMQ" без контекста - у каждого свои сценарии.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью