Как паттерн Saga обеспечивает целостность данных между микросервисами и как его реализовать?

Ответ

Паттерн Saga — это механизм управления распределенными транзакциями, который обеспечивает целостность данных в микросервисной архитектуре без использования блокирующих двухфазных коммитов (2PC).

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

Способы реализации Saga:

  1. Choreography (Хореография):

    • Нет центрального координатора.
    • Сервисы общаются друг с другом через асинхронные события (events).
    • Каждый сервис подписывается на события других сервисов и выполняет свою часть работы, после чего публикует собственное событие.
    • Плюсы: Слабая связанность (loosely coupled).
    • Минусы: Сложно отслеживать состояние всей транзакции, риск циклических зависимостей.
  2. Orchestration (Оркестрация):

    • Есть центральный сервис-оркестратор, который управляет всем процессом.
    • Оркестратор вызывает сервисы в нужной последовательности и отвечает за запуск компенсирующих транзакций в случае сбоя.
    • Плюсы: Централизованная логика, легко отслеживать состояние и добавлять новые шаги.
    • Минусы: Оркестратор может стать узким местом и точкой отказа.

Пример псевдокода для Оркестрации:

// OrderSagaOrchestrator управляет процессом создания заказа
func OrderSagaOrchestrator(orderData Order) error {
    // Шаг 1: Создать заказ
    if err := orderService.CreateOrder(orderData); err != nil {
        return err // Первый шаг не требует компенсации
    }

    // Шаг 2: Зарезервировать товары на складе
    if err := inventoryService.ReserveInventory(orderData.Items); err != nil {
        // Компенсация для шага 1
        orderService.CancelOrder(orderData.ID)
        return err
    }

    // Шаг 3: Обработать платеж
    if err := paymentService.ProcessPayment(orderData.Amount); err != nil {
        // Компенсация для шага 2
        inventoryService.ReleaseInventory(orderData.Items)
        // Компенсация для шага 1
        orderService.CancelOrder(orderData.ID)
        return err
    }

    return nil // Сага успешно завершена
}

Ключевые принципы реализации:

  • Идемпотентность: Все операции (как прямые, так и компенсирующие) должны быть идемпотентными, чтобы повторный вызов не приводил к ошибкам или дублированию данных.
  • Надежность компенсаций: Компенсирующие транзакции не должны завершаться сбоем. Если это возможно, они должны быть атомарными и надежными.
  • Сохранение состояния: Оркестратор должен персистентно сохранять состояние саги, чтобы восстановить процесс после сбоя.