
簡介
在整體架構中,業務事務可以在同一個 ACID 事務中包含多個資料庫操作。在微服務中,每個服務都有自己的資料庫-不能使用傳統的分散式交易(2PC),因為它會產生緊密耦合並影響效能。傳奇模式是一種替代方案。
1. 分散式事務的問題
1.1 兩階段提交(2PC)-為什麼不適合
2PC Flow:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Transaction │ │ Service A │ │ Service B │
│ Coordinator │ │ (Order) │ │ (Payment) │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
│── Phase 1: PREPARE ──────────────────▶│
│◀── VOTE YES ────────────────────────── │
│── Phase 1: PREPARE ──▶│ │
│◀── VOTE YES ──────── │ │
│ │ │
│── Phase 2: COMMIT ──▶│ │
│── Phase 2: COMMIT ──────────────────▶│
│ │ │
微服務中的2PC問題:
| 問題 | 說明 |
|---|---|
| 單點故障 | 協調員死亡 → 所有參與者均被鎖定 |
| 效能瓶頸 | 2PC 期間鎖定資源 |
| 緊密耦合 | 所有服務必須同時可用 |
| 不可擴展 | 鎖爭用隨著參與者數量的增加而增加 |
| 網路分區 | 如果網路在階段 1 與階段 2 之間中斷 → 狀態不一致 |
1.2 CAP 定理重複
在分散式系統中,只能實現三分之二:
- 一致性:每次讀取都會傳回最新的寫入
- 可用性:每個請求都會收到回應
- 分區容錯:網路分裂時系統繼續運作
微服務選擇AP(可用性+分割區容錯)→接受最終一致性。
2.傳奇模式
2.1 概念
Saga 是一系列本地事務,每個事務都由一個服務執行。如果某個步驟失敗,saga 會執行補償交易來撤銷先前的步驟。
Saga = T1 → T2 → T3 → ... → Tn
Nếu Ti fail:
Compensate: C(i-1) → C(i-2) → ... → C1
Ví dụ Order Saga:
T1: Create Order (status: PENDING)
T2: Reserve Payment
T3: Reserve Inventory
T4: Confirm Order (status: CONFIRMED)
Nếu T3 fail:
C2: Refund Payment
C1: Cancel Order (status: CANCELLED)
2.2 補償交易
saga中的每筆交易都必須有一個相應的補償交易:
| 步驟 | 行動 | 補償行動 |
|---|---|---|
| T1 | 建立訂單 | 取消訂單 |
| T2 | 預訂付款 | 退款付款 |
| T3 | 儲備庫存 | 發布庫存 |
| T4 | 出貨時間表 | 取消運送 |
| T5 | 傳送確認電子郵件 | 傳送取消電子郵件 |
重要說明:
- 補償事務必須是冪等(多次運行具有相同的結果)
- 補償事務不能失敗(必須重試直到成功)
- 某些操作無法補償(例如,發送已發送的電子郵件)→使用「語意撤銷」(發送已取消的電子郵件)
3.編舞傳奇
3.1 概念
每個服務發布事件當完成本地事務時,下一個服務訂閱並處理:
┌──────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Order │ │ Payment │ │ Inventory │ │ Shipping │
│ Service │ │ Service │ │ Service │ │ Service │
└────┬─────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │ │
│── OrderCreated ▶│ │ │
│ │── PaymentReserved ▶│ │
│ │ │── InventoryReserved ▶│
│ │ │ │── ShipmentScheduled
│◀───────────────────────────────────────────────────────── │
│ OrderConfirmed │ │ │
│ │ │ │
│ === FAILURE === │ │ │
│ │ │── InventoryFailed ▶│
│ │◀─ CompensatePayment─ │
│◀─ PaymentRefunded─ │ │
│ OrderCancelled │ │ │
3.2 實作範例
// Order Service — publishes OrderCreated
@Service
public class OrderService {
@Transactional
public Order createOrder(CreateOrderCommand cmd) {
Order order = new Order(cmd.getCustomerId(), cmd.getItems());
order.setStatus(OrderStatus.PENDING);
orderRepository.save(order);
// Publish event
eventPublisher.publish(new OrderCreatedEvent(
order.getId(),
order.getCustomerId(),
order.getItems(),
order.getTotalAmount()
));
return order;
}
// Compensating handler
@EventHandler
public void on(PaymentFailedEvent event) {
Order order = orderRepository.findById(event.getOrderId());
order.setStatus(OrderStatus.CANCELLED);
order.setFailureReason(event.getReason());
orderRepository.save(order);
}
}
// Payment Service — listens to OrderCreated
@Service
public class PaymentService {
@EventHandler
public void on(OrderCreatedEvent event) {
try {
Payment payment = paymentGateway.reserve(
event.getCustomerId(),
event.getTotalAmount()
);
eventPublisher.publish(new PaymentReservedEvent(
event.getOrderId(),
payment.getId()
));
} catch (InsufficientFundsException e) {
eventPublisher.publish(new PaymentFailedEvent(
event.getOrderId(),
"Insufficient funds"
));
}
}
}
3.3 優點和缺點
| 優勢 | 缺點 |
|---|---|
| 服務之間的鬆散耦合 | 難以追蹤複雜的流程 |
| 簡單,步驟少 | 傳奇故事可能會發生循環依賴 |
| 不存在單點故障 | 出現錯誤時難以除錯 |
| 事件驅動架構自然而然 | 複雜測試 |
4.編排傳奇
4.1 概念
中央 Saga Orchestrator 協調整個工作流程,向每個服務發送命令並處理回應:
┌──────────────────────┐
│ Order Saga │
│ Orchestrator │
│ │
│ State Machine: │
│ CREATED │
│ → PAYMENT_PENDING │
│ → INVENTORY_PENDING │
│ → SHIPPING_PENDING │
│ → CONFIRMED │
│ or → COMPENSATING │
│ → CANCELLED │
└────────┬─────────────┘
│
┌────────────────┼────────────────┐
│ │ │
┌──────▼──────┐ ┌─────▼──────┐ ┌──────▼──────┐
│ Payment │ │ Inventory │ │ Shipping │
│ Service │ │ Service │ │ Service │
└─────────────┘ └────────────┘ └─────────────┘
4.2 狀態機實現
public class OrderSaga {
public enum State {
CREATED,
PAYMENT_PENDING,
PAYMENT_RESERVED,
INVENTORY_PENDING,
INVENTORY_RESERVED,
SHIPPING_PENDING,
CONFIRMED,
COMPENSATING_INVENTORY,
COMPENSATING_PAYMENT,
CANCELLED
}
@Autowired
private SagaRepository sagaRepository;
public void start(CreateOrderCommand cmd) {
SagaState saga = new SagaState(cmd.getOrderId(), State.CREATED);
sagaRepository.save(saga);
// Step 1: Reserve Payment
saga.setState(State.PAYMENT_PENDING);
commandGateway.send(new ReservePaymentCommand(
cmd.getOrderId(), cmd.getAmount()
));
}
@SagaEventHandler
public void on(PaymentReservedEvent event) {
SagaState saga = sagaRepository.findByOrderId(event.getOrderId());
saga.setState(State.INVENTORY_PENDING);
// Step 2: Reserve Inventory
commandGateway.send(new ReserveInventoryCommand(
event.getOrderId(), saga.getItems()
));
}
@SagaEventHandler
public void on(InventoryReservedEvent event) {
SagaState saga = sagaRepository.findByOrderId(event.getOrderId());
saga.setState(State.SHIPPING_PENDING);
// Step 3: Schedule Shipping
commandGateway.send(new ScheduleShippingCommand(
event.getOrderId(), saga.getAddress()
));
}
@SagaEventHandler
public void on(ShipmentScheduledEvent event) {
SagaState saga = sagaRepository.findByOrderId(event.getOrderId());
saga.setState(State.CONFIRMED);
commandGateway.send(new ConfirmOrderCommand(event.getOrderId()));
}
// === COMPENSATION ===
@SagaEventHandler
public void on(InventoryReservationFailedEvent event) {
SagaState saga = sagaRepository.findByOrderId(event.getOrderId());
saga.setState(State.COMPENSATING_PAYMENT);
// Compensate: Refund Payment
commandGateway.send(new RefundPaymentCommand(
event.getOrderId(), saga.getPaymentId()
));
}
@SagaEventHandler
public void on(PaymentRefundedEvent event) {
SagaState saga = sagaRepository.findByOrderId(event.getOrderId());
saga.setState(State.CANCELLED);
commandGateway.send(new CancelOrderCommand(
event.getOrderId(), "Inventory not available"
));
}
}
4.3 優點和缺點
| 優勢 | 缺點 |
|---|---|
| 易於理解的工作流程(集中式) | Orchestrator 存在單點故障 |
| 易於調試和監控 | 神級風險(邏輯太多) |
| 易於新增/刪除步驟 | 編排器與服務之間的耦合 |
| 薪酬邏輯清晰 | 需要持久的 saga 狀態 |
5. 比較編排與編排
| 標準 | 編舞 | 編排 |
|---|---|---|
| 聯軸器 | 很寬鬆 | 媒介(透過協調器) |
| 複雜性 | 服務數量增加 | 專注於編排器 |
| 能見度 | 整體流程難以看清 | 明確地在狀態機中 |
| 故障處理 | 驅散 | 焦點 |
| 測試 | 困難(分散式) | 更容易(測試協調器) |
| 可擴展性 | 好 | 好(編曲家無國籍) |
| 推薦 | 傳奇中≤ 4 個服務 | > 4 項服務或複雜流程 |
6. 錯誤處理與死信佇列
6.1 死信佇列(DLQ)
多次重試仍無法處理的訊息傳入DLQ:
┌─────────────┐
│ Main Queue │
│ (order.cmds)│
└──────┬──────┘
│
┌──────▼──────┐
│ Consumer │
│ (Service) │
└──────┬──────┘
│
Success? ─┼─ No (after max retries)
│ │
▼ ▼
┌─────┐ ┌──────────┐
│ ACK │ │ DLQ │
└─────┘ │(order. │
│ cmds.dlq)│
└──────────┘
│
┌────▼────┐
│ Alert + │
│ Manual │
│ Review │
└─────────┘
6.2 冪等性
確保多次處理該訊息以獲得相同的結果:
@Service
public class PaymentService {
@EventHandler
public void on(ReservePaymentCommand cmd) {
// Idempotency check
String idempotencyKey = "payment:" + cmd.getOrderId();
if (processedStore.exists(idempotencyKey)) {
log.info("Already processed payment for order {}", cmd.getOrderId());
return; // Skip duplicate
}
Payment payment = processPayment(cmd);
// Mark as processed
processedStore.save(idempotencyKey, payment.getId());
}
}
6.3 傳奇超時
public class OrderSaga {
@SagaTimeout(duration = "5m")
public void onTimeout(SagaState saga) {
log.warn("Saga timeout for order {}", saga.getOrderId());
// Compensate based on current state
switch (saga.getState()) {
case INVENTORY_PENDING:
compensatePayment(saga);
break;
case SHIPPING_PENDING:
compensateInventory(saga);
compensatePayment(saga);
break;
}
saga.setState(State.CANCELLED);
saga.setFailureReason("Saga timeout");
}
}
7. 最佳實踐
7.1 Saga 設計指南
1. Mỗi step phải có compensating action
2. Compensating actions phải idempotent
3. Sử dụng correlation ID (saga ID) xuyên suốt
4. Persist saga state (survive service restart)
5. Set timeout cho mỗi saga instance
6. Monitor saga metrics (success rate, duration, failure reasons)
7. DLQ cho messages không xử lý được
8. Tránh saga quá nhiều steps (> 7 steps → xem lại design)
7.2 選擇編排還是編排?
Flow đơn giản (2-4 services)?
└── Choreography
Flow phức tạp (> 4 services, conditional logic)?
└── Orchestration
Cần visibility cao vào business process?
└── Orchestration
Muốn minimize coupling?
└── Choreography
Team experience với event-driven?
├── Nhiều → Choreography
└── Ít → Orchestration
總結
- 由於緊密耦合和鎖爭用,2PC 不適合微服務
- Saga模式使用本地交易鏈+補償交易
- 編排:服務透過事件進行通信,分散
- 編排:Saga Orchestrator 透過狀態機集中協調
- 補償事務必須冪等且不能失敗
- 使用DLQ、逾時和冪等性進行錯誤處理