Распределенные системы состоят из множества независимых сервисов, которые взаимодействуют друг с другом. Управление транзакциями в таких системах является сложной задачей, так как стандартный механизм транзакций (ACID) не всегда применим. В распределенных системах используются специальные подходы, обеспечивающие согласованность данных.
Подходы к управлению транзакциями
Двухфазный коммит (2PC)
Двухфазный коммит (2-Phase Commit, 2PC) является классическим протоколом координации транзакций. Он состоит из двух этапов:
- Фаза подготовки (Prepare phase) — координатор отправляет запрос всем участникам транзакции, ожидая их согласия.
- Фаза фиксации (Commit phase) — если все участники готовы, координатор отправляет команду фиксации транзакции. В случае сбоя транзакция откатывается.
Этот метод гарантирует согласованность, но может создавать узкое место в системе из-за необходимости ожидания всех участников.
Саги (Saga Pattern)
Сага — это последовательность локальных транзакций, объединенных логикой компенсации. Если одна из транзакций терпит неудачу, запускаются компенсирующие операции для отката изменений.
Паттерн саги бывает двух типов:
- Оркестрация — централизованный сервис управляет выполнением шагов саги.
- Хореография — каждый сервис самостоятельно реагирует на события и выполняет свои действия.
Этот метод лучше масштабируется по сравнению с 2PC, но требует тщательного проектирования компенсирующих операций.
Eventual Consistency
Принцип eventual consistency (конечной согласованности) допускает временные расхождения в данных между сервисами, но гарантирует, что со временем они придут к единому состоянию. Этот подход часто используется в асинхронных системах с событиями и очередями сообщений.
Реализация в C#
Использование Outbox Pattern с RabbitMQ
Outbox Pattern помогает избежать потери сообщений при взаимодействии сервисов. Он заключается в сохранении сообщений в локальной базе данных перед отправкой в очередь.
public class OrderService
{
private readonly ApplicationDbContext _context;
private readonly IMessageBus _messageBus;
public OrderService(ApplicationDbContext context, IMessageBus messageBus)
{
_context = context;
_messageBus = messageBus;
}
public void CreateOrder(Order order)
{
using var transaction = _context.Database.BeginTransaction();
_context.Orders.Add(order);
_context.OutboxMessages.Add(new OutboxMessage
{
EventType = "OrderCreated",
Payload = JsonSerializer.Serialize(order)
});
_context.SaveChanges();
transaction.Commit();
_messageBus.Publish(order);
}
}
Этот подход гарантирует, что сообщения будут отправлены только после успешного завершения транзакции.
Выбор подхода к управлению транзакциями зависит от требований системы. Двухфазный коммит обеспечивает строгую согласованность, но снижает масштабируемость. Саги позволяют строить гибкие и отказоустойчивые системы, но требуют продуманной обработки ошибок. Принцип конечной согласованности используется в высоконагруженных системах, где допустимы временные расхождения в данных.