Как обеспечить консистентность данных в распределенной системе (микросервисная архитектура)?

Ответ

В микросервисной архитектуре классические ACID-транзакции, охватывающие несколько сервисов, не применяются из-за сильной связанности и проблем с производительностью. Вместо этого достигается конечная согласованность (eventual consistency) с помощью паттернов распределенных транзакций.

Основной паттерн — Saga.

Saga — это последовательность локальных транзакций. Каждый шаг саги выполняет транзакцию в одном сервисе и публикует событие, которое запускает следующий шаг в другом сервисе. Если шаг завершается неудачно, сага выполняет компенсирующие транзакции для отмены предыдущих шагов.

Существует два способа координации саги:

  1. Хореография (Choreography): сервисы общаются напрямую через события, без центрального координатора. Это обеспечивает слабую связанность.

    Пример создания заказа:

    // OrderService
    func (s *Service) CreateOrder(orderData) error {
        // 1. Локальная транзакция: сохранить заказ в статусе "pending"
        if err := s.db.SaveOrderAsPending(orderData); err != nil {
            return err
        }
        // 2. Публикация события для следующего шага
        return s.eventBus.Publish("OrderCreated", orderData)
    }
    
    // PaymentService (подписчик на "OrderCreated")
    func (s *Service) OnOrderCreated(event) error {
        if err := s.db.ChargePayment(); err != nil {
            // Публикуем компенсирующее событие
            s.eventBus.Publish("PaymentFailed", event.OrderID)
            return err
        }
        // Публикуем событие об успешной оплате
        s.eventBus.Publish("PaymentSucceeded", event.OrderID)
    }
  2. Оркестрация (Orchestration): центральный сервис (оркестратор) управляет всем процессом, отправляя команды каждому участнику и ожидая ответа.

Связанные паттерны:

  • Outbox Pattern: Гарантирует, что событие будет отправлено тогда и только тогда, когда локальная транзакция успешно завершена. Изменения и события записываются в одну транзакционную таблицу "outbox", а отдельный процесс считывает ее и надежно доставляет сообщения.
  • CQRS (Command Query Responsibility Segregation): Часто используется вместе с сагами. Разделение моделей на запись (Commands) и чтение (Queries) упрощает обработку событий и построение согласованных представлений данных.