← Назад к вопросам

Что такое распределенная транзакция в контексте микросервисной архитектуры?

2.8 Senior🔥 111 комментариев
#REST API и микросервисы#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Распределённые транзакции в микросервисной архитектуре

Распределённая транзакция — это механизм обеспечения консистентности данных при выполнении операций, затрагивающих несколько независимых сервисов или базах данных. В микросервисной архитектуре этот вопрос становится критически важным, так как традиционные ACID транзакции неприменимы.

Проблема транзакций в микросервисах

В монолитной архитектуре с одной БД всё просто:

@Transactional
public void transferMoney(Long fromAccount, Long toAccount, BigDecimal amount) {
    account1.debit(amount);      // Все операции в одной транзакции
    account2.credit(amount);
}

Но в микросервисах у нас разные сервисы с разными БД:

  • Сервис счётов (Account Service)
  • Сервис кошельков (Wallet Service)
  • Сервис истории (History Service)

Традиционные ACID транзакции через разные БД невозможны.

Подход Two-Phase Commit (2PC)

Two-Phase Commit — классический способ для распределённых транзакций, хотя и не очень подходит для микросервисов.

Фаза 1: Prepare (Подготовка)

  • Координатор запрашивает у всех участников готовность
  • Каждый участник блокирует ресурсы и проверяет возможность выполнения

Фаза 2: Commit (Подтверждение)

  • Если все согласны — координатор отправляет commit
  • Все участники завершают операцию
// Пример с XA (eXtended Architecture)
@Service
public class TransferService {
    @Transactional(timeout = 10)  // Зависит от поддержки БД
    public void transferBetweenServices(String from, String to, BigDecimal amount) {
        // Фаза подготовки и коммита управляется контейнером
        accountService.debit(from, amount);
        walletService.credit(to, amount);
    }
}

Проблемы 2PC:

  • Блокировки ресурсов снижают производительность
  • Не масштабируется для высоконагруженных систем
  • Требует синхронного взаимодействия
  • Сложность в облачных и распределённых сетях

Паттерн Saga (Реплицирующаяся транзакция)

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

Orchestration (Оркестрация) — управляется центральным сервисом:

@Service
public class TransferSagaOrchestrator {
    private final AccountServiceClient accountService;
    private final WalletServiceClient walletService;
    private final SagaLogRepository sagaLog;
    
    public void executeTransfer(TransferRequest request) {
        String transactionId = UUID.randomUUID().toString();
        
        try {
            // Шаг 1: Дебет счёта
            accountService.debit(request.getFromAccount(), request.getAmount());
            sagaLog.log(transactionId, "ACCOUNT_DEBITED");
            
            // Шаг 2: Кредит кошелька
            walletService.credit(request.getToWallet(), request.getAmount());
            sagaLog.log(transactionId, "WALLET_CREDITED");
            
            // Шаг 3: Запись в историю
            historyService.record(new HistoryEntry(request));
            sagaLog.log(transactionId, "HISTORY_RECORDED");
            
        } catch (Exception e) {
            compensate(transactionId);
            throw e;
        }
    }
    
    private void compensate(String transactionId) {
        // Откат в обратном порядке
        List<String> executedSteps = sagaLog.getSteps(transactionId);
        
        if (executedSteps.contains("HISTORY_RECORDED")) {
            historyService.deleteRecord(transactionId);
        }
        if (executedSteps.contains("WALLET_CREDITED")) {
            walletService.reverse(transactionId);
        }
        if (executedSteps.contains("ACCOUNT_DEBITED")) {
            accountService.reverse(transactionId);
        }
    }
}

Choreography (Хореография) — управляется событиями (Event-Driven):

// Сервис счётов
@Service
public class AccountService {
    @Transactional
    public void debit(String accountId, BigDecimal amount) {
        Account account = accountRepository.findById(accountId);
        account.setBalance(account.getBalance().subtract(amount));
        accountRepository.save(account);
        
        // Публикуем событие
        eventPublisher.publishEvent(
            new AccountDebitedEvent(accountId, amount)
        );
    }
    
    @EventListener
    public void onWalletCreditFailed(WalletCreditFailedEvent event) {
        // Компенсирующая операция
        Account account = accountRepository.findById(event.getAccountId());
        account.setBalance(account.getBalance().add(event.getAmount()));
        accountRepository.save(account);
    }
}

// Сервис кошелька
@Service
public class WalletService {
    @EventListener
    public void onAccountDebited(AccountDebitedEvent event) {
        try {
            Wallet wallet = walletRepository.findById(event.getWalletId());
            wallet.setBalance(wallet.getBalance().add(event.getAmount()));
            walletRepository.save(wallet);
            
            eventPublisher.publishEvent(
                new WalletCreditedEvent(event.getWalletId(), event.getAmount())
            );
        } catch (Exception e) {
            eventPublisher.publishEvent(
                new WalletCreditFailedEvent(event.getAccountId(), event.getAmount())
            );
        }
    }
}

Паттерн Outbox (Transactional Outbox)

Для гарантированной доставки событий используется Outbox паттерн:

@Entity
public class OutboxEvent {
    @Id
    private UUID id;
    private String aggregateId;
    private String eventType;
    private String payload;
    private LocalDateTime createdAt;
    private boolean published;
}

@Service
public class AccountService {
    @Transactional
    public void debit(String accountId, BigDecimal amount) {
        // Локальная транзакция
        Account account = accountRepository.findById(accountId);
        account.setBalance(account.getBalance().subtract(amount));
        accountRepository.save(account);
        
        // В той же транзакции записываем в Outbox
        OutboxEvent event = new OutboxEvent(
            UUID.randomUUID(),
            accountId,
            "ACCOUNT_DEBITED",
            new AccountDebitedEvent(accountId, amount).toJson()
        );
        outboxRepository.save(event);
    }
}

// Отдельный процесс публикует события из Outbox
@Scheduled(fixedDelay = 1000)
public void publishOutboxEvents() {
    List<OutboxEvent> unpublished = outboxRepository.findByPublishedFalse();
    for (OutboxEvent event : unpublished) {
        try {
            eventPublisher.publish(event.getPayload());
            event.setPublished(true);
            outboxRepository.save(event);
        } catch (Exception e) {
            log.error("Failed to publish event: {}", event.getId(), e);
        }
    }
}

Идемпотентность и дедупликация

Важный аспект распределённых транзакций — гарантирование идемпотентности:

@Service
public class PaymentService {
    private final IdempotencyKeyRepository idempotencyKeyRepository;
    
    public PaymentResult processPayment(String idempotencyKey, PaymentRequest request) {
        // Проверяем, не обрабатывали ли уже этот запрос
        IdempotencyKey key = idempotencyKeyRepository.findById(idempotencyKey)
            .orElse(null);
        
        if (key != null && key.isProcessed()) {
            return key.getResult();
        }
        
        try {
            PaymentResult result = executePayment(request);
            idempotencyKeyRepository.save(new IdempotencyKey(
                idempotencyKey,
                true,
                result
            ));
            return result;
        } catch (Exception e) {
            idempotencyKeyRepository.save(new IdempotencyKey(
                idempotencyKey,
                false,
                null
            ));
            throw e;
        }
    }
}

Инструменты и фреймворки

  • Apache Camel — для оркестрации сервисов
  • Temporal — engine для распределённых workflow
  • Dapr (Distributed Application Runtime) — абстракция для распределённых паттернов
  • Axon Framework — поддержка Event Sourcing и CQRS
  • Spring Cloud — встроенная поддержка микросервисных паттернов

Выбор подхода

  • 2PC: Низкая нагрузка, синхронное взаимодействие, требование ACID
  • Orchestration Saga: Готовый центральный сервис, чёткие workflow
  • Choreography Saga: Автономные сервисы, асинхронность, масштабируемость
  • Event Sourcing + Outbox: Высокие требования к надёжности и audit trail

Выбор подхода зависит от требований консистентности, масштабируемости и сложности вашей архитектуры.

Что такое распределенная транзакция в контексте микросервисной архитектуры? | PrepBro