Что такое паттерн Saga и как он применяется в распределенных системах?

Ответ

Saga — это паттерн управления транзакциями в распределенных системах (например, в микросервисной архитектуре), который помогает поддерживать целостность данных без использования традиционных ACID-транзакций и блокировок.

Вместо одной большой транзакции, Saga разбивает бизнес-процесс на последовательность локальных транзакций. Каждая из этих транзакций атомарна и выполняется в рамках одного сервиса.

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

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

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

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

    • Существует центральный сервис-координатор (оркестратор), который управляет всем процессом.
    • Оркестратор последовательно вызывает нужные сервисы для выполнения локальных транзакций. В случае сбоя он же отвечает за вызов компенсирующих транзакций.
    • Плюсы: Централизованная логика, проще понимать и отлаживать процесс.
    • Минусы: Оркестратор может стать единой точкой отказа (Single Point of Failure).

Пример концепции на Go (Оркестрация):

// Каждый шаг — это вызов отдельного микросервиса
func placeOrderSaga(orderID string, userID string, money float64) error {
    // Шаг 1: Зарезервировать товары
    if err := inventoryService.ReserveItems(orderID); err != nil {
        return fmt.Errorf("ошибка резервации: %w", err)
    }

    // Шаг 2: Списать средства с клиента
    if err := paymentService.Charge(userID, money); err != nil {
        // Компенсация для шага 1
        inventoryService.CancelReservation(orderID)
        return fmt.Errorf("ошибка оплаты: %w", err)
    }

    // Шаг 3: Создать доставку
    if err := shippingService.CreateShipment(orderID); err != nil {
        // Компенсация для шага 2 и 1
        paymentService.Refund(userID, money)
        inventoryService.CancelReservation(orderID)
        return fmt.Errorf("ошибка доставки: %w", err)
    }

    return nil
}