> Как модифицировать архитектуру микросервиса, чтобы обеспечить надежность обработки заказов при падении аналитического сервиса (Go)

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

Компании: Ozon

Стек: Go

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

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

Надёжность обработки заказов при падении аналитического сервиса достигается через отделение критического пути от некритического: аналитика должна потреблять события асинхронно, через брокер сообщений или outbox-паттерн, а не участвовать в синхронном запросе. Заказ фиксируется в своей БД, после чего публикуется событие; аналитический сервис становится потребителем, и его недоступность не влияет на приём заказов. Дополнительно - ретраи, dead letter queue и идемпотентность.

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

Проблема: если аналитический сервис вызывается синхронно внутри обработки заказа (например, для валидации, обогащения или записи метрик), его падение приводит к ошибке или таймауту всего запроса. Клиент не получает подтверждение, заказ может быть потерян или продублирован.

Решение - разделение на две части:

  1. Критический путь - приём заказа, валидация, сохранение в собственную БД, возврат ответа клиенту. Здесь участвуют только сервис заказов и его хранилище.
  2. Некритический путь - аналитика, аудит, метрики, рекомендации. Всё, что можно сделать позже, делается асинхронно.

Архитектурные варианты:

  • Брокер сообщений (Kafka, RabbitMQ, NATS): сервис заказов публикует событие order_created после коммита транзакции. Аналитический сервис подписан на топик. Если он лежит - события накапливаются в брокере, после восстановления он их дочитает.
  • Outbox-паттерн: в той же транзакции, что и запись заказа, пишется запись в таблицу outbox. Отдельный релизер (в Go - горутина с ticker) читает outbox и отправляет события в брокер. Это гарантирует, что событие не потеряется даже при падении между записью заказа и публикацией.
  • Полный отказ от синхронных вызовов: аналитический сервис не вызывается из обработчика заказа вообще. Он читает данные из своей копии (например, из CDC-стрима) или из событий.

Дополнительные меры надёжности:

  • Ретраи с экспоненциальной задержкой на стороне потребителя.
  • Dead letter queue - если событие не обработалось после N попыток, оно уходит в DLQ для ручного разбора, но не блокирует основной поток.
  • Идемпотентность - аналитический сервис должен переживать повторную доставку события (ключ - order_id).
  • Graceful degradation - если аналитика нужна для ответа клиенту (например, расчёт стоимости), то при её недоступности можно вернуть ответ с дефолтными значениями или пометить заказ как "требует уточнения".

На практике

В Go это выглядит так:

  • Сервис заказов: POST /orders → валидация → запись в PostgreSQL (заказ + outbox в одной транзакции) → ответ 201.
  • Отдельная горутина (или отдельный сервис order-relayer) читает outbox, отправляет в Kafka, помечает отправленным.
  • Аналитический сервис: consumer из Kafka, обработка с идемпотентностью по order_id, ретраи через sarama или segmentio/kafka-go.

Важно: не использовать синхронный HTTP-вызов к аналитике внутри обработчика. Если аналитика нужна для бизнес-логики (например, скоринг), то либо выносим её в отдельный шаг с асинхронным подтверждением, либо делаем fallback.

Пример кода

GO
// Обработчик создания заказа
func (s *OrderService) CreateOrder(ctx context.Context, req CreateOrderRequest) (*Order, error) {
order := &Order{ID: uuid.New(), Status: "created", ...}
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return nil, err
}
defer tx.Rollback()
// 1. Пишем заказ
if err := s.repo.InsertOrder(ctx, tx, order); err != nil {
return nil, err
}
// 2. Пишем в outbox в той же транзакции
outbox := OutboxEvent{
ID: uuid.New(),
Aggregate: "order",
EventType: "order_created",
Payload: order,
CreatedAt: time.Now(),
}
if err := s.repo.InsertOutbox(ctx, tx, outbox); err != nil {
return nil, err
}
// 3. Коммит - только после этого событие может быть отправлено
if err := tx.Commit(); err != nil {
return nil, err
}
return order, nil
}
// Релизер outbox - отдельная горутина
func (s *OrderService) RunOutboxRelayer(ctx context.Context) {
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
events, err := s.repo.GetUnsentOutbox(ctx, 100)
if err != nil {
log.Printf("outbox read error: %v", err)
continue
}
for _, ev := range events {
if err := s.producer.Publish(ctx, "orders", ev); err != nil {
log.Printf("publish failed: %v", err)
continue // оставляем как unsent, повторим позже
}
if err := s.repo.MarkOutboxSent(ctx, ev.ID); err != nil {
log.Printf("mark sent failed: %v", err)
}
}
}
}
}

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

Начни с ключевой идеи: аналитика не должна быть на критическом пути. Затем опиши два подхода - брокер и outbox, и объясни, почему outbox предпочтительнее для гарантии доставки. Упомяни идемпотентность и DLQ как обязательные элементы. Если спросят про альтернативы - скажи про CDC (Debezium) или event sourcing. Не углубляйся в детали реализации конкретного брокера, если не просят. Заверши trade-off: асинхронность добавляет сложность (консистентность в конечном счёте, дубликаты), но радикально повышает доступность.

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

  • Понимание разницы между синхронной и асинхронной интеграцией.
  • Знание паттернов: outbox, transactional outbox, event-driven architecture.
  • Умение проектировать отказоустойчивость: ретраи, DLQ, идемпотентность.
  • Понимание trade-off: согласованность vs доступность (CAP).
  • Практический опыт с Go: горутины, транзакции, работа с брокерами.

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

  • Предлагать синхронный вызов с таймаутом и ретраем - это не решает проблему, а лишь откладывает её.
  • Игнорировать outbox и полагаться только на публикацию в брокер после коммита - есть окно потери события.
  • Не упоминать идемпотентность - дубликаты событий неизбежны.
  • Говорить "просто добавь очередь" без объяснения, как гарантировать доставку и порядок.
  • Забывать про DLQ - без него "мёртвые" события будут вечно крутиться в ретраях.

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

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