Как реализовать распределённые транзакции в микросервисах
В микросервисах нет единой базы данных, поэтому обычные транзакции (ACID) не работают.
🟢 Junior Level
В микросервисах нет единой базы данных, поэтому обычные транзакции (ACID) не работают.
Два подхода:
- Saga — последовательность локальных транзакций с компенсациями
- Two-Phase Commit (2PC) — протоколы распределённых транзакций (редко используется)
Saga популярнее 2PC, потому что не блокирует ресурсы на время всей транзакции и работает с гетерогенными БД, тогда как 2PC требует поддержки двухфазного коммита от всех участников.
Заказ товара:
1. Order Service: создать заказ (локальная транзакция)
2. Payment Service: списать деньги (локальная транзакция)
3. Inventory Service: зарезервировать товар (локальная транзакция)
Если шаг 2 упал → отменить заказ (компенсация)
Цель: Eventual consistency — данные согласованы, но не мгновенно.
Когда НЕ реализовывать распределённые транзакции
Если можно перепроектировать так, чтобы один сервис владел всеми данными — избегайте распределённых транзакций полностью. Это самое надёжное решение.
🟡 Middle Level
Реализация Saga
1. Choreography (event-driven):
// Order Service
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
eventPublisher.publish(new OrderCreatedEvent(order.getId()));
}
// Payment Service
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
try {
paymentService.charge(event.orderId());
eventPublisher.publish(new PaymentCompletedEvent(event.orderId()));
} catch (Exception e) {
eventPublisher.publish(new PaymentFailedEvent(event.orderId(), e));
}
}
// Order Service — компенсация
@EventListener
public void onPaymentFailed(PaymentFailedEvent event) {
orderService.cancelOrder(event.orderId());
eventPublisher.publish(new OrderCancelledEvent(event.orderId()));
}
2. Orchestration (coordinator):
@Component
public class OrderOrchestrator {
public void processOrder(Order order) {
try {
orderService.create(order);
paymentService.charge(order);
inventoryService.reserve(order);
} catch (PaymentException e) {
orderService.cancel(order);
throw e;
} catch (InventoryException e) {
paymentService.refund(order);
orderService.cancel(order);
throw e;
}
}
}
Типичные ошибки
- Отсутствие идемпотентности:
Событие доставлено дважды → payment списается дважды! Решение: проверяйте orderID перед обработкой - Потерянные события:
Event publisher упал → событие не доставлено Решение: transactional outbox pattern
🔴 Senior Level
Transactional Outbox Pattern
Outbox — специальная таблица в той же БД, куда в одной транзакции с бизнес-данными пишется событие. CDC (Change Data Capture, Debezium) читает логи БД и отправляет события в Kafka.
// Проблема: как гарантировать доставку событий?
// Решение: Outbox table в той же транзакции
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
outboxRepository.save(new OutboxEvent(
"OrderCreated", order.getId(), payload
));
// Оба сохраняются в одной транзакции!
}
// CDC (Change Data Capture) или polling читает outbox
// и публикует в Kafka
@Component
public class OutboxPublisher {
@Scheduled(fixedDelay = 1000)
public void publishPending() {
List<OutboxEvent> events = outboxRepository.findPending();
for (OutboxEvent event : events) {
kafkaTemplate.send(event.getTopic(), event.getPayload());
outboxRepository.markPublished(event.getId());
}
}
}
Архитектурные Trade-offs
| Подход | Плюсы | Минусы |
|---|---|---|
| Saga | Гибкий, масштабируемый | Сложная компенсация |
| 2PC | Строгая согласованность | Блокировки, медленный |
| Eventual | Простой | Нет гарантий |
Edge Cases
1. Concurrent Sagas:
Saga1: Order A → reserve inventory item1
Saga2: Order B → reserve inventory item1
Конкуренция → один из заказов провалится
Решение: optimistic/pessimistic locking
2. Saga timeout:
// Пример иллюстративный (Axon Framework). Реальный API зависит от библиотеки.
// Защита от "зависших" Saga
@SagaConfiguration
public class OrderSagaConfig {
@Bean
public SagaConfiguration<OrderSaga> orderSaga() {
return SagaConfiguration.sagaManagedBy(OrderSaga.class)
.sagaTimeout(Duration.ofMinutes(5));
}
}
Production Experience
Debezium CDC + Kafka:
Database → Debezium → Kafka → Event Consumers
Outbox table → Debezium CDC → Kafka topic → Saga handlers
Это гарантирует:
- Доставка хотя бы один раз (at-least-once)
- Порядок событий
- Восстановление после сбоев
Best Practices
✅ Используйте Transactional Outbox
✅ Реализуйте idempotency на каждом шаге
✅ Добавьте Saga timeout
✅ Логируйте каждый шаг
✅ Мониторьте длительность Saga
❌ Не используйте 2PC без необходимости
❌ Не игнорируйте concurrent Sagas
❌ Не забывайте про компенсации
🎯 Шпаргалка для интервью
Обязательно знать:
- Два основных подхода: Saga и Two-Phase Commit (2PC)
- Saga популярнее: не блокирует ресурсы, работает с разными БД
- Transactional Outbox гарантирует доставку событий (write в одной транзакции с бизнес-данными)
- CDC (Debezium) читает outbox и публикует в Kafka
- Idempotency обязательна на каждом шаге
- Concurrent Sagas требуют locking механизмов
- Saga timeout защищает от зависших транзакций
- Лучший подход — избежать распределённых транзакций перепроектированием
Частые уточняющие вопросы:
- Почему Saga лучше 2PC? 2PC блокирует ресурсы на всю транзакцию и требует поддержки от всех участников. Saga — асинхронна и гибка.
- Как гарантировать доставку событий? Transactional Outbox + CDC — запись события в той же транзакции что и бизнес-данные.
- Что такое eventual consistency? Данные станут согласованными через время, но не мгновенно.
- Как обрабатывать concurrent Sagas? Optimistic/pessimistic locking на уровне ресурсов.
Красные флаги (НЕ говорить):
- “2PC — лучшее решение для микросервисов” — нет, слишком медленный и блокирующий
- “Outbox не нужен, Kafka гарантирует доставку” — Kafka гарантирует доставку, но не атомарность с БД
- “Eventual consistency = данные могут быть неверными” — нет, они станут верными
- “Распределённые транзакции не нужны, можно всё в одном сервисе” — не всегда возможно
Связанные темы:
- [[1. Что такое паттерн Saga и когда его использовать]]
- [[2. В чём разница между хореографией и оркестрацией в Saga]]
- [[4. Что такое компенсирующие транзакции]]
- [[13. Что такое паттерн Database per Service]]
- [[16. В чём разница между синхронной и асинхронной коммуникацией]]