Ответ
Распределенная транзакция — это операция, которая охватывает несколько независимых ресурсов (разные базы данных, очереди сообщений, микросервисы) и должна быть выполнена как единое атомарное действие (ACID на уровне распределенной системы). Это означает «все или ничего»: либо все участники фиксируют изменения, либо все откатываются.
Классический подход: Двухфазный коммит (2PC)
Протокол, где координатор управляет процессом.
Фаза 1: Подготовка (Prepare)
- Координатор отправляет всем участникам команду
prepare. - Каждый участник выполняет локальные операции (включая запись в лог), но не фиксирует их, блокируя ресурсы.
- Участник отвечает
YES(готов к фиксации) илиNO(не может выполнить).
Фаза 2: Фиксация или Откат (Commit/Rollback)
- Если все ответили
YES, координатор рассылает командуcommit. Участники фиксируют изменения. - Если хотя бы один ответил
NO, координатор рассылает командуrollback. Все участники откатывают изменения.
Пример на Java с JTA (Java Transaction API):
import javax.transaction.UserTransaction;
import javax.annotation.Resource;
import javax.persistence.*;
@Stateless
public class DistributedTransferService {
@Resource
private UserTransaction userTransaction; // Координатор транзакций
@PersistenceContext(unitName = "AccountDB")
private EntityManager emAccount; // Ресурс 1: База счетов
@PersistenceContext(unitName = "AuditDB")
private EntityManager emAudit; // Ресурс 2: База аудита
public void transferMoney(Long fromId, Long toId, BigDecimal amount) throws Exception {
userTransaction.begin(); // Начало распределенной транзакции
try {
// 1. Списание со счета отправителя (Resource 1)
Account from = emAccount.find(Account.class, fromId);
from.withdraw(amount);
// 2. Запись в лог аудита (Resource 2)
AuditLog logEntry = new AuditLog("Transfer", fromId, toId, amount);
emAudit.persist(logEntry);
// 3. Зачисление на счет получателя (Resource 1)
Account to = emAccount.find(Account.class, toId);
to.deposit(amount);
userTransaction.commit(); // Фаза 2: Commit, если все успешно
} catch (Exception e) {
userTransaction.rollback(); // Фаза 2: Rollback при любой ошибке
throw e;
}
}
}
Проблемы 2PC и современные альтернативы
- Блокировки ресурсов: Участники блокируют данные на время всей транзакции (проблема производительности).
- Координатор — единая точка отказа.
- Сложность восстановления после сбоя на фазе подготовки.
Современные паттерны для микросервисов:
- Saga: Длинная транзакция, состоящая из цепочки локальных транзакций. Каждая следующая транзакция запускается после успеха предыдущей. В случае ошибки выполняются компенсирующие транзакции (Compensating Transactions) для отката уже выполненных шагов.
- Eventual Consistency (Конечная согласованность): Отказ от немедленной атомарности. Система допускает временную несогласованность, но гарантирует, что в конечном счете все данные будут согласованы (через обмен событиями).
Когда использовать? 2PC подходит для монолитных приложений с несколькими БД. Для микросервисной архитектуры предпочтительны Saga или Eventual Consistency.