> Что происходит при передаче данных между сервисами с брокером сообщений если сервис недоступен (Go)

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

Компании: Wildberries

Стек: Go

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

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

Если сервис-потребитель недоступен, сообщение остаётся в очереди брокера до истечения TTL или до повторного подключения. Брокер гарантирует at-least-once delivery: сообщение не теряется, но возможны дубликаты. Если недоступен продюсер - сообщения просто не публикуются, очередь деградирует по данным. Ключевой момент: брокер не удаляет сообщения при недоступности консьюмера, если не настроены политики dead letter queue или TTL. В Go это особенно важно из-за конкурентной модели обработки.

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

При передаче через брокер (RabbitMQ, Kafka, NATS) недоступность сервиса влияет по-разному в зависимости от роли:

Недоступен консьюмер - сообщения накапливаются в очереди. Брокер продолжает принимать от продюсера, но не может доставить. Поведение зависит от настроек:

  • Без TTL - сообщения хранятся бесконечно (в RabbitMQ - до перезапуска брокера, в Kafka - до истечения retention)
  • С TTL - сообщения удаляются после истечения срока
  • С DLQ - после N неудачных попыток сообщение перемещается в dead letter queue
  • В Kafka consumer group - партиции продолжают хранить данные, offset не двигается

Недоступен продюсер - очередь пустеет, консьюмеры либо блокируются на чтении, либо получают пустые ответы. В Kafka это не проблема - данные уже в логе.

Критический аспект - подтверждения (ack). Если консьюмер получил сообщение, но упал до ack, брокер вернёт сообщение в очередь (requeue). Это гарантирует доставку, но создаёт риск дубликатов при идемпотентной обработке.

В Go важно учитывать, что стандартные клиенты (например, github.com/rabbitmq/amqp091-go) работают в горутинах. Если консьюмер падает, соединение рвётся, и брокер переустанавливает канал. Нужна обработка ErrClosed и реконнект с экспоненциальным backoff.

На практике

Типичный сценарий для senior - проектирование отказоустойчивости:

  1. Настроить retry с backoff на стороне консьюмера
  2. Использовать DLQ для сообщений, которые не удалось обработать после N попыток
  3. Для критичных данных - не использовать TTL, а полагаться на ручную обработку DLQ
  4. В Kafka - настроить enable.auto.commit=false и коммитить offset только после успешной обработки
  5. Мониторить глубину очереди (queue depth) как ключевой метрику

Пример из практики: сервис оплаты шлёт событие в очередь, сервис уведомлений недоступен. Если не настроен DLQ - через 3 дня сообщение уйдёт в DLQ, и его нужно обработать вручную или повторно запушить. Если очередь без TTL - она растёт, и при восстановлении сервиса он получит лавину старых сообщений.

Пример кода

GO
// Консьюмер с реконнектом и retry в Go
func consume(ctx context.Context, conn *amqp.Connection, queue string) error {
for {
ch, err := conn.Channel()
if err != nil {
return err
}
msgs, err := ch.Consume(queue, "", false, false, false, false, nil)
if err != nil {
return err
}
for {
select {
case <-ctx.Done():
return nil
case msg, ok := <-msgs:
if !ok {
// канал закрыт - реконнект
ch.Close()
time.Sleep(5 * time.Second)
goto reconnect
}
if err := processMessage(msg.Body); err != nil {
// retry с backoff, потом в DLQ
if retries := getRetries(msg); retries < 3 {
msg.Nack(false, true) // requeue
} else {
msg.Nack(false, false) // в DLQ
}
continue
}
msg.Ack(false)
}
}
reconnect:
}
}

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

Начни с чёткого разделения ролей: продюсер и консьюмер. Скажи, что брокер - это буфер, и его основная задача - не потерять данные. Затем переходи к деталям: ack, requeue, DLQ, TTL. Для senior важно показать понимание trade-off между гарантиями доставки и производительностью.

Упомяни, что в Kafka модель отличается от RabbitMQ: там нет очереди в классическом смысле, а есть лог с offset. Если консьюмер недоступен - offset не двигается, и при восстановлении он прочитает все сообщения с последнего коммита.

Хорошо добавить про идемпотентность: при at-least-once доставке дубликаты неизбежны, поэтому обработчик должен быть идемпотентным. Это покажет глубину понимания.

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

  • Понимание гарантий доставки (at-most-once, at-least-once, exactly-once)
  • Знание механик брокеров: ack, requeue, DLQ, TTL
  • Умение проектировать отказоустойчивые системы
  • Понимание разницы между RabbitMQ и Kafka
  • Навыки работы с конкурентностью в Go (горутины, каналы, контексты)

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

  • Путаница между requeue и DLQ: requeue возвращает в ту же очередь, DLQ - в отдельную
  • Утверждение, что сообщение теряется при недоступности консьюмера - это неверно для большинства брокеров
  • Игнорирование проблемы дубликатов: при at-least-once они гарантированы
  • Отсутствие обработки закрытия канала в Go - без реконнекта консьюмер молча умирает
  • Непонимание, что TTL - это потеря данных, а не решение проблемы недоступности
  • Слишком долгий retry без backoff - при недоступности сервиса это создаст нагрузку на брокер

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

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