Шаблон «Сага»: распределенные транзакции в архитектуре микросервисов

Шаблон «Сага»: распределенные транзакции в архитектуре микросервисов

В традиционных монолитных приложениях поддерживать согласованность данных между несколькими объектами не составляет труда. Ядра реляционных баз данных предоставляют гарантии ACID (атомарность, согласованность, изоляция, долговечность), заключенные в локальные транзакции SQL. Если размещение заказа, удержание платежа или резервирование запасов завершаются неудачей на полпути, вызов ROLLBACK мгновенно отменяет все изменения в базе данных.

Однако при переходе на современную архитектуру микросервисов управление данными существенно меняется. Чтобы обеспечить автономию домена и независимую масштабируемость, каждый микросервис владеет собственной базой данных. Одна бизнес-операция, такая как обработка заказа электронной коммерции, теперь охватывает несколько границ служб и механизмов баз данных (например, PostgreSQL для заказов, DynamoDB для платежей, Redis для инвентаризации).

Поскольку распределенные микросервисы не могут полагаться на одну транзакцию базы данных, поддержание согласованности данных в пределах границ сети становится одной из самых сложных проблем в разработке распределенных систем.

Чтобы решить эту проблему, не жертвуя доступностью или производительностью системы, архитекторы программного обеспечения полагаются на Saga Pattern.

В этом глубоком погружении мы исследуем, почему традиционные распределенные транзакции терпят неудачу, разберем базовую механику шаблона Saga, сравним Хореографию и оркестровку, проанализируем контрмеры изоляции, изучим реализации производственного кода в Go и Java и научимся безопасно обрабатывать откаты при реальных сбоях.


Основная проблема: почему двухфазная фиксация (2PC) не работает в микросервисах

Прежде чем принять шаблон Saga, инженеры часто задаются вопросом: Почему мы не можем использовать традиционную двухфазную фиксацию (2PC/XA) в наших микросервисах?

Механика 2PC

Двухфазная фиксация координирует распределенную транзакцию между несколькими узлами базы данных с помощью центрального диспетчера транзакций в два этапа:

  1. Фаза подготовки: Менеджер транзакций просит все участвующие узлы базы данных подготовить и заблокировать необходимые строки. Участники голосуют за YES или NO.
  2. Фаза фиксации: если все участники проголосовали за YES, менеджер выдает команду COMMIT всем узлам. Если какой-либо узел проголосовал за NO или истекло время ожидания, он выдает ROLLBACK.
Client ----> Transaction Manager
                   |
     +-------------+-------------+
     | (Prepare)   | (Prepare)   | (Prepare)
     v             v             v
 Order DB     Payment DB    Inventory DB

Почему 2PC — это антишаблон для микросервисов

Хотя 2PC гарантирует строгую согласованность, в облачных средах микросервисов он не работает из-за нескольких архитектурных недостатков:

  1. Блокировка и конфликт ресурсов: строки базы данных остаются заблокированными на протяжении всего многоэтапного сетевого установления связи. Если задержка в сети увеличивается или работа службы замедляется, блокировки остаются открытыми, быстро занимая пулы подключений и вызывая каскадный сбой системы.
  2. Узкое место в доступности. В 2PC доступность системы ограничена произведением доступности всех участников ($A_{total} = A_1 \times A_2 \times \dots \times A_n$). Если какой-либо отдельный узел службы или базы данных отключится от сети на этапе подготовки, вся глобальная транзакция блокируется на неопределенный срок.
  3. Потеря автономии служб: 2PC вынуждает службы предоставлять протоколы XA уровня базы данных через сетевые API, напрямую связывая свои механизмы баз данных.
  4. Ограничения брокеров. Брокеры сообщений с высокой пропускной способностью, такие как Apache Kafka или RabbitMQ, изначально не участвуют в традиционных транзакциях XA 2PC в реляционных базах данных.

Согласно Теореме CAP, распределенные системы должны выбирать между строгой согласованностью (C) и высокой доступностью (A) в разделе сетевых разделов (P). Современные системы микросервисов отдают приоритет Доступности и устойчивости к разделению, обменивая мгновенную согласованность на Эвентуальную согласованность (БАЗА: Базовая доступность, Мягкое состояние, Итоговая согласованность).


Что такое шаблон саги?

Шаблон Saga был первоначально предложен Гектором Гарсиа-Молиной и Кеннетом Салемом в 1987 году как механизм обработки долгоживущих транзакций в системах управления базами данных. В современных микросервисах Сага представляет собой последовательность дискретных локальных транзакций.

Вместо того, чтобы заключать весь мультисервисный поток в одну глобальную блокировку, Saga выполняет бизнес-процесс как серию независимых транзакций локальной базы данных ($T_1, T_2, \dots, T_n$).

Каждая локальная транзакция обновляет данные в базе данных одного микросервиса и публикует событие или сообщение домена. Это событие запускает следующую локальную транзакцию ($T_{i+1}$) в нижестоящей службе.

[Order Service]         [Payment Service]       [Inventory Service]
  Local Tx T1 -------------> Local Tx T2 -------------> Local Tx T3
(Create Order)              (Process Payment)            (Reserve Stock)

Прямое выполнение и обратная компенсация

Если все локальные транзакции завершаются успешно, Saga завершается успешно (прямое выполнение).

Однако если локальная транзакция завершается неудачно на полпути (например, $T_3$ завершается неудачно из-за отсутствия товара на складе), Saga не может просто вызвать базу данных ROLLBACK для предыдущих шагов ($T_1, T_2$), поскольку эти локальные транзакции уже зафиксированы в соответствующих базах данных.

Чтобы отменить уже зафиксированные локальные транзакции, Saga должна выполнить Компенсирующие транзакции ($C_{n-1}, \dots, C_1$) в обратном порядке.

Forward Flow:    T1 (Create Order) ---> T2 (Charge Card) ---> T3 (Reserve Inventory - FAILS)
                                                                       |
Backward Rollback:  C1 (Cancel Order) <--- C2 (Refund Card) <----------+

Классификация транзакций Saga

Чтобы разработать надежный рабочий процесс Saga, каждый шаг в последовательности транзакций должен быть отнесен к одному из трех структурных типов:

Тип транзакции Описание Требование идемпотентности и отката
Компенсационные сделки Шаги, выполненные до точки невозврата. Их можно отменить или отменить, если на следующем этапе произошел сбой. Должна быть соответствующая компенсирующая транзакция ($C_i$).
Сводная сделка Решающий шаг в саге. Если поворот окажется успешным, сага гарантированно завершится. Если это не удается, Сага откатывается. Ни компенсируемому, ни возвратному; он отмечает границу между откатом и завершением вперед.
Повторные транзакции Шаги, выполняемые после транзакции Pivot. В конечном итоге они гарантированно добьются успеха и не требуют компенсации. Должны быть строго идемпотентными, поскольку попытки будут повторяться автоматически до успешного завершения.

Пример проверки электронной коммерции: разбивка

Рассмотрим оформление заказа в электронной коммерции, состоящее из четырех шагов:

  1. $T_1$: Создать отложенный ордер (Компенсируемый) $\to$ Отменить через $C_1$: Отменить ордер.
  2. $T_2$: Авторизовать платеж (Компенсируемый) $\to$ Отмена через $C_2$: Возврат платежа.
  3. $T_3$: Резервный запас (Основная транзакция) $\to$ Если распределение запасов прошло успешно, заказ завершается. В случае неудачи активируйте $C_2$ и $C_1$.
  4. $T_4$: Запрос на отправку (Повторно) $\to$ Выполняется после поворота; повторял попытку до доставки.

Архитектурные стили Saga: хореография против оркестровки

Существует два основных архитектурных стиля для реализации шаблона Saga в распределенных системах: Хореография (децентрализованная) и Оркестровка (централизованная).

Стили реализации шаблона Saga: хореография и оркестровка. Архитектурная схема

Стиль 1: Хореография (децентрализация, управляемая событиями)

В Саге, основанной на хореографии, нет центрального контролера или координатора. Вместо этого микросервисы взаимодействуют асинхронно, прослушивая события домена, опубликованные в центральной шине событий (например, Apache Kafka, NATS или RabbitMQ).

Механика рабочего процесса

  1. Служба заказов выполняет $T_1$ (создает отложенный ордер) и отправляет событие OrderCreated в Kafka.
  2. Платежный сервис слушает OrderCreated, выполняет $T_2$ (списание средств с кредитной карты) и генерирует событие PaymentCompleted (или PaymentFailed).
  3. Служба инвентаризации слушает PaymentCompleted, выполняет $T_3$ (резервирует элементы). Если товара нет в наличии, выдается InventoryReservationFailed.
  4. Платежный сервис слушает InventoryReservationFailed и выполняет $C_2$ (выдает возврат средств).
  5. Служба заказов слушает PaymentRefunded и выполняет $C_1$ (помечает заказ как отмененный).

Преимущества хореографии

  • Слабая связь: сервисы подписываются только на темы событий; они не знают о других реализациях сервисов.
  • Высокая пропускная способность и децентрализация: прямая публикация/подписка событий устраняет узкие места центрального оркестратора.
  • Простота для коротких рабочих процессов: легко настроить для простых 2–3-этапных рабочих процессов.

Недостатки хореографии

  • Риск циклической зависимости: службы могут в конечном итоге прослушивать события друг друга, создавая сложные циклические зависимости.
  • Сложность отслеживания кода. Понимание сквозного бизнес-процесса требует отслеживания логики в нескольких репозиториях кодовой базы. – Шторм событий и сложность: по мере увеличения количества шагов (например, более 10 сервисов) управление пограничными случаями ошибок приводит к взрывному росту состояния событий.

Стиль 2: оркестровка (централизованное управление рабочими процессами)

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

Механика рабочего процесса

  1. Клиент отправляет запрос заказа в Saga Orchestrator.
  2. Оркестратор отправляет команду CreateOrder в Заказать услугу. Служба заказов возвращает OrderCreated.
  3. Оркестратор обновляет свой конечный автомат и отправляет команду ProcessPayment в Платежный сервис. Платежный сервис возвращает PaymentSuccessful.
  4. Оркестратор отправляет команду ReserveInventory в Inventory Service. Служба инвентаризации возвращает InventoryFailed (Out of Stock).
  5. Оркестратор обнаруживает сбой, инициирует поток компенсации:
    • Отправляет команду RefundPayment в Платежный сервис.
    • Отправляет команду CancelOrder в Заказать услугу.
  6. Оркестратор помечает выполнение Saga как FAILED.

Преимущества оркестрации

  • Централизованная бизнес-логика: состояние рабочего процесса и бизнес-логика локализуются в одной службе оркестратора или конечном автомате.
  • Нет циклических зависимостей: микросервисы отвечают на команды оркестратора; они не зависят от других нижестоящих услуг и не знают о них.
  • Четкий мониторинг и отладка: состояние сквозной транзакции явно сохраняется в хранилище состояний оркестратора (например, PostgreSQL или механизмах рабочих процессов, таких как Temporal/Camunda).
  • Упрощенная обработка ошибок: добавление новых шагов или изменение правил отката полностью управляется внутри оркестратора.

Недостатки оркестрации

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

Сравнительная матрица: хореография против оркестровки

Особенность Хореография Оркестровка
Структура управления Децентрализованный (Event Pub/Sub) Централизованный (координатор саги/конечный автомат)
Соединение Чрезвычайно низкий (сервисы потребляют события) Средний (Сервисы принимают команды от Координатора)
Видимость процесса Низкий (распределяется по файлам журналов) Высокий (единое хранилище состояний визуализирует рабочий процесс)
Лучше всего подходит для Простые рабочие процессы (от 2 до 4 этапов обслуживания) Сложные рабочие процессы предприятия (5+ шагов, логика ветвления)
Инструменты/фреймворки Кафка, RabbitMQ, NATS, AWS EventBridge Temporal.io, Шаговые функции AWS, Camunda, Axon

Проблемы изоляции и меры противодействия (обращение с «кислотой минус I»)

Поскольку локальные транзакции в Saga немедленно фиксируются в своих локальных базах данных, в шаблоне Saga отсутствует изоляция (I) по сравнению с традиционными гарантиями ACID.

Если клиент читает строку базы данных, измененную $T_1$, пока Saga все еще работает, он читает незафиксированное, промежуточное состояние. Если на следующем этапе происходит сбой и активируется компенсация ($C_1$), клиент выполнил грязное чтение.

Распространенные аномалии, вызванные отсутствием изоляции

  1. Утеряны обновления: Saga A обновляет запись. Прежде чем Сага А завершится, Сага Б перезапишет ту же запись. Если Saga A завершается сбоем и выполняет компенсацию, обновление Saga B перезаписывается.
  2. Грязное чтение: клиент читает доступные запасы, обновленные Saga A ($T_1$). Сага А выходит из строя ($T_3$) и восстанавливает запасы ($C_1$), но клиент уже разместил заказ на основе устаревших данных.
  3. Неповторяющееся чтение: служба считывает данные на этапе $T_1$ и считывает их снова на этапе $T_3$, но другая параллельная Saga изменила данные между ними.

Контрмеры и стратегии смягчения последствий

Чтобы поддерживать целостность данных, несмотря на отсутствие изоляции, архитекторы программного обеспечения реализуют определенные шаблоны проектирования изоляции:

1. Семантическая блокировка (ожидание/состояние флага)

Когда локальная транзакция $T_1$ обновляет запись базы данных, она устанавливает поле статуса в PENDING или APPROVAL_REQUIRED (например, ORDER_PENDING_PAYMENT).

Другие саги, одновременно читающие эту запись, должны проверить флаг семантической блокировки и заблокировать или изменить свое поведение до тех пор, пока состояние не изменится на COMMITTED или CANCELLED.

2. Отправка заказа

Спроектируйте последовательность локальных транзакций так, чтобы операции с высоким риском или необратимые события происходили на поздних стадиях выполнения Saga, что сводит к минимуму окно уязвимости.

3. Повторное чтение проверки (оптимистическое управление параллелизмом)

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

4. Пессимистический взгляд

Измените порядок шагов саги, чтобы минимизировать экономические риски (например, разместите авторизацию платежа как можно ближе к основной транзакции).


Последовательность операций производства: компенсирующие транзакции

Ниже приведена полная блок-схема последовательности операций, иллюстрирующая сбой прямого выполнения и последующее выполнение обратной компенсации:

Блок-схема последовательности шаблонов Saga, показывающая сбой прямого выполнения и компенсирующие откаты

Практическая реализация кода

Давайте рассмотрим готовые к использованию примеры реализации хореографии (в Go) и оркестрации (в Java Spring Boot).

Реализация 1: Сага на основе хореографии в Go

В этом примере Go мы демонстрируем Службу заказов, обрабатывающую создание заказов и прослушивающую события сбоя платежа через брокера событий для выполнения компенсирующей логики отката.

package saga

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"time"
)

// Event definitions
type OrderCreatedEvent struct {
	OrderID   string  `json:"order_id"`
	CustomerID string `json:"customer_id"`
	Amount    float64 `json:"amount"`
}

type PaymentFailedEvent struct {
	OrderID string `json:"order_id"`
	Reason  string `json:"reason"`
}

// OrderRepository handles local DB operations
type OrderRepository interface {
	CreateOrder(ctx context.Context, orderID string, amount float64) error
	UpdateOrderStatus(ctx context.Context, orderID string, status string) error
}

// EventBus abstraction for message broker (e.g., Kafka / NATS)
type EventBus interface {
	Publish(topic string, payload []byte) error
	Subscribe(topic string, handler func(payload []byte)) error
}

type OrderSagaChoreographer struct {
	repo     OrderRepository
	eventBus EventBus
}

func NewOrderSagaChoreographer(repo OrderRepository, bus EventBus) *OrderSagaChoreographer {
	c := &OrderSagaChoreographer{repo: repo, eventBus: bus}
	c.registerSubscriptions()
	return c
}

// Step 1: Forward Transaction (T1)
func (s *OrderSagaChoreographer) StartOrderSaga(ctx context.Context, orderID, customerID string, amount float64) error {
	// Execute local database transaction
	err := s.repo.CreateOrder(ctx, orderID, amount)
	if err != nil {
		return fmt.Errorf("failed local DB transaction T1: %w", err)
	}

	// Emit domain event for downstream Payment Service
	event := OrderCreatedEvent{OrderID: orderID, CustomerID: customerID, Amount: amount}
	bytes, _ := json.Marshal(event)
	
	log.Printf("[SAGA][T1] Order %s created. Publishing OrderCreatedEvent...", orderID)
	return s.eventBus.Publish("orders.created", bytes)
}

// Register subscription for compensating events
func (s *OrderSagaChoreographer) registerSubscriptions() {
	_ = s.eventBus.Subscribe("payments.failed", func(payload []byte) {
		var event PaymentFailedEvent
		if err := json.Unmarshal(payload, &event); err != nil {
			log.Printf("[ERROR] Corrupt payment event: %v", err)
			return
		}

		// Execute Compensating Transaction (C1)
		s.handlePaymentFailed(context.Background(), event)
	})
}

// Step C1: Compensating Transaction
func (s *OrderSagaChoreographer) handlePaymentFailed(ctx context.Context, event PaymentFailedEvent) {
	log.Printf("[SAGA][C1] Payment failed for Order %s (Reason: %s). Rolling back local order...", event.OrderID, event.Reason)
	
	// Revert order status to CANCELLED in local DB
	err := s.repo.UpdateOrderStatus(ctx, event.OrderID, "CANCELLED_PAYMENT_FAILED")
	if err != nil {
		log.Printf("[CRITICAL] Failed to execute compensating transaction C1 for Order %s: %v", event.OrderID, err)
		// Trigger alert or write to Dead Letter Queue (DLQ)
		return
	}
	
	log.Printf("[SAGA][SUCCESS] Order %s successfully compensated and cancelled.", event.OrderID)
}

Реализация 2: Сага на основе оркестрации в Java (Spring Boot)

В этом примере Java мы создаем Saga Orchestrator, используя шаблон конечного автомата для координации прямых команд и выполнения компенсирующих откатов в случае сбоя последующего шага.

package com.ghaznix.saga.orchestrator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.UUID;

public enum SagaState {
    STARTED,
    ORDER_CREATED,
    PAYMENT_PROCESSED,
    INVENTORY_RESERVED,
    COMPLETED,
    COMPENSATING_PAYMENT,
    COMPENSATING_ORDER,
    FAILED
}

@Service
public class OrderSagaOrchestrator {

    private static final Logger log = LoggerFactory.getLogger(OrderSagaOrchestrator.class);

    private final OrderServiceClient orderClient;
    private final PaymentServiceClient paymentClient;
    private final InventoryServiceClient inventoryClient;

    public OrderSagaOrchestrator(OrderServiceClient orderClient,
                                PaymentServiceClient paymentClient,
                                InventoryServiceClient inventoryClient) {
        this.orderClient = orderClient;
        this.paymentClient = paymentClient;
        this.inventoryClient = inventoryClient;
    }

    public boolean executeOrderSaga(String customerId, String productId, double amount, int quantity) {
        String sagaId = UUID.randomUUID().toString();
        log.info("[SAGA {}] Starting Order Saga Workflow...", sagaId);
        
        SagaState currentState = SagaState.STARTED;
        String orderId = null;
        String paymentId = null;

        try {
            // Step 1: Forward Local Tx T1 - Create Order
            log.info("[SAGA {}][T1] Sending CreateOrder command...", sagaId);
            orderId = orderClient.createOrder(customerId, productId, amount);
            currentState = SagaState.ORDER_CREATED;

            // Step 2: Forward Local Tx T2 - Process Payment
            log.info("[SAGA {}][T2] Sending ProcessPayment command for Order {}...", sagaId, orderId);
            paymentId = paymentClient.chargePayment(orderId, customerId, amount);
            currentState = SagaState.PAYMENT_PROCESSED;

            // Step 3: Forward Local Tx T3 (Pivot Step) - Reserve Inventory
            log.info("[SAGA {}][T3] Sending ReserveInventory command for Product {}...", sagaId, productId);
            boolean stockReserved = inventoryClient.reserveStock(productId, quantity);

            if (!stockReserved) {
                throw new InventoryAllocationException("Stock allocation failed: Item out of stock.");
            }

            currentState = SagaState.INVENTORY_RESERVED;
            log.info("[SAGA {}][SUCCESS] Saga completed successfully!", sagaId);
            return true;

        } catch (Exception ex) {
            log.error("[SAGA {}][FAILURE] Step failed during state {}. Triggering Compensation...", sagaId, currentState, ex);
            rollbackSaga(sagaId, currentState, orderId, paymentId);
            return false;
        }
    }

    private void rollbackSaga(String sagaId, SagaState failedState, String orderId, String paymentId) {
        log.info("[SAGA {}] Initiating backward compensating transactions from state: {}", sagaId, failedState);

        // Compensate Step 2 if payment was processed
        if (failedState == SagaState.PAYMENT_PROCESSED || failedState == SagaState.INVENTORY_RESERVED) {
            try {
                log.info("[SAGA {}][C2] Executing Payment Refund compensation for Payment {}...", sagaId, paymentId);
                paymentClient.refundPayment(paymentId);
            } catch (Exception e) {
                log.error("[CRITICAL][SAGA {}] Payment refund C2 failed! Manual intervention or DLQ required.", sagaId, e);
            }
        }

        // Compensate Step 1 if order was created
        if (failedState != SagaState.STARTED && orderId != null) {
            try {
                log.info("[SAGA {}][C1] Executing Order Cancel compensation for Order {}...", sagaId, orderId);
                orderClient.cancelOrder(orderId);
            } catch (Exception e) {
                log.error("[CRITICAL][SAGA {}] Order cancellation C1 failed!", sagaId, e);
            }
        }

        log.info("[SAGA {}] Saga compensation workflow finished. Final State: FAILED.", sagaId);
    }
}

Основы производства: сочетание Saga с шаблоном транзакционных исходящих сообщений

Как в хореографии, так и в оркестровке выполнение локальной транзакции ($T_i$) требует публикации события или команды домена по сети.

Если ваш сервис обновляет свою базу данных SQL, а затем публикует сообщение в Kafka, сбой в сети после фиксации базы данных приводит к тихой потере событий. И наоборот, публикация сообщения до фиксации базы данных приводит к обработке фантомных событий.

Чтобы решить эту проблему, Sagas необходимо объединить с Шаблоном транзакционных исходящих сообщений:

[Service Database Transaction Boundary]
+-----------------------------------------------------+
| 1. INSERT INTO business_table (orders/payments)    |
| 2. INSERT INTO outbox_table (event_payload)        |
+-----------------------------------------------------+
                          |
             (CDC / Polling Message Relay)
                          |
                          v
               [Message Broker / Kafka]

Сохраняя полезные данные события в таблице outbox внутри той же транзакции локальной базы данных, атомарность гарантируется. Асинхронный фоновый процесс (например, Debezium или реле опроса) считывает данные из таблицы исходящих сообщений и надежно публикует события в Kafka.

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


Контрольный список архитектурного решения

Используйте эту практическую матрицу решений при разработке распределенных транзакций для приложений микросервисов:

                  Do you need cross-service data consistency?
                                     |
                    +----------------+----------------+
                    | No                              | Yes
                    v                                 v
         Standard Single Service            Can you accept Eventual
            Local Database                    Consistency (BASE)?
                                                      |
                                     +----------------+----------------+
                                     | No                              | Yes
                                     v                                 v
                          Use Monolithic Core            Adopt Saga Pattern
                          with Single ACID DB                 |
                                                              |
                                             How complex is the workflow?
                                                              |
                                            +-----------------+-----------------+
                                            | Simple (2-3 steps)                | Complex (4+ steps/branches)
                                            v                                   v
                                   Choreography Saga                   Orchestration Saga
                                   (Event-Driven Bus)                  (Temporal/Custom State Machine)

Заключение и ключевые выводы

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

Сводный контрольный список:

  1. Отказ от 2PC/XA в облачных микросервисах: двухфазная фиксация приводит к жесткой блокировке, высокой задержке и серьезным проблемам с доступностью.
  2. Разбейте транзакции на локальные шаги. Разделите глобальные операции на локальные транзакции ($T_1 \dots T_n$) в сочетании с обратными компенсирующими транзакциями ($C_1 \dots C_{n-1}$).
  3. Выберите правильный архитектурный стиль: – Используйте Хореографию для простых 2–3-шаговых потоков, управляемых событиями, со слабой связью.
    • Используйте Оркестрацию для сложных бизнес-процессов, требующих централизованной видимости, ветвления и отслеживания конечных автоматов.
  4. Внедрите меры по изоляции. Защитите себя от несанкционированного чтения и потери обновлений с помощью семантических блокировок (флаги PENDING) и оптимистической блокировки повторного чтения.
  5. Гарантируйте надежный обмен сообщениями. Всегда сочетайте реализации Saga с шаблоном транзакционных исходящих сообщений и применяйте идемпотентных потребителей для безопасной обработки повторных попыток.

Вдумчиво реализуя шаблон Saga, вы можете создавать масштабируемые микросервисы с высокой доступностью, которые остаются устойчивыми и согласованными даже в случае сбоя сети.