Saga 事务

某电商平台的订单系统需要处理一个超长链路:创建订单 → 扣库存 → 扣余额 → 扣积分 → 发优惠券 → 发短信 → 发邮件。

团队评估了 TCC 方案,发现每个服务都要实现 Try-Confirm-Cancel 三个方法,工作量巨大。

最终选择了 Saga 模式:每个服务只需要实现"正向操作"和"补偿操作",通过编排器协调整个事务流程。

【架构权衡】 Saga 模式是最适合"长链路、多服务"场景的分布式事务方案。与 TCC 不同,Saga 不需要 Try 阶段,每个服务只需要实现"做"和"撤销"两个操作。这大大降低了实现复杂度,但牺牲了"隔离性"——Saga 没有 TCC 那样的资源预留机制。


一、核心问题 🔴

1.1 Saga vs TCC

对比TCCSaga
阶段Try-Confirm-Cancel(3阶段)Do-Undo/Redo(2阶段)
锁定资源有(通过 Try 预留)
补偿方式Confirm/Cancel补偿(Compensation)
一致性最终一致最终一致
隔离性有(通过 Try 预留)无(天然弱隔离)
实现复杂度
适用场景短链路长链路

1.2 Saga 的两种模式

┌─────────────────────────────────────────────────────────────────┐
│                       Saga 的两种模式                             │
│                                                                  │
│  1. 编排式 Saga(Choreography)                                   │
│     ├─ 每个服务订阅其他服务的事件                                  │
│     ├─ 通过事件驱动协调                                           │
│     └─ 适合:链路短、服务少                                       │
│                                                                  │
│     示例:                                                       │
│     OrderService ──► InventoryService ──► PaymentService           │
│        ▲               ▲                                           │
│        │               │                                           │
│        └───────────────┘                                           │
│              事件驱动                                             │
│                                                                  │
│  2. 编排器 Saga(Orchestration)                                  │
│     ├─ 中央编排器控制整个事务流程                                  │
│     ├─ 编排器调用各服务的 API                                     │
│     └─ 适合:链路长、服务多                                       │
│                                                                  │
│     示例:                                                       │
│                    ┌─────────────────┐                          │
│                    │   Orchestrator   │                          │
│                    └────────┬────────┘                          │
│              ┌─────────────┼─────────────┐                      │
│              ▼             ▼             ▼                        │
│        OrderService   InventoryService  PaymentService             │
└─────────────────────────────────────────────────────────────────┘

二、方案实现

2.1 编排器 Saga 实现

// Saga 编排器
@Service
public class OrderSagaOrchestrator {

    @Autowired
    private OrderService orderService;
    @Autowired
    private InventoryService inventoryService;
    @Autowired
    private PaymentService paymentService;
    @Autowired
    private SagaLogRepository sagaLogRepository;

    public boolean executeOrderSaga(OrderSagaRequest request) {
        String sagaId = UUID.randomUUID().toString();

        try {
            // 1. 创建订单
            String orderId = orderService.createOrder(request.getUserId(), request.getItems());
            sagaLogRepository.save(sagaId, "CreateOrder", "SUCCESS", orderId);

            // 2. 扣库存
            try {
                inventoryService.deductStock(request.getItems());
                sagaLogRepository.save(sagaId, "DeductStock", "SUCCESS", null);
            } catch (StockException e) {
                // 补偿:取消订单
                orderService.cancelOrder(orderId);
                sagaLogRepository.save(sagaId, "DeductStock", "COMPENSATED", null);
                return false;
            }

            // 3. 扣余额
            try {
                paymentService.deductBalance(request.getUserId(), request.getAmount());
                sagaLogRepository.save(sagaId, "DeductBalance", "SUCCESS", null);
            } catch (BalanceException e) {
                // 补偿:恢复库存 + 取消订单
                inventoryService.restoreStock(request.getItems());
                orderService.cancelOrder(orderId);
                sagaLogRepository.save(sagaId, "DeductBalance", "COMPENSATED", null);
                return false;
            }

            // 4. 发送通知(可选,不影响主事务)
            try {
                notificationService.sendOrderNotification(orderId);
            } catch (Exception e) {
                // 通知失败不影响主事务
                log.warn("发送通知失败", e);
            }

            sagaLogRepository.save(sagaId, "Saga", "COMPLETED", orderId);
            return true;

        } catch (Exception e) {
            log.error("Saga 执行失败", e);
            return false;
        }
    }
}

2.2 Saga 的补偿策略

// Saga 补偿策略
@Service
public class SagaCompensationExecutor {

    public void compensate(String sagaId) {
        // 1. 从 Saga 日志中获取所有已执行的操作
        List<SagaLog> executedSteps = sagaLogRepository.findExecutedSteps(sagaId);

        // 2. 逆序执行补偿
        Collections.reverse(executedSteps);

        for (SagaLog step : executedSteps) {
            if ("CreateOrder".equals(step.getStepName())) {
                // 补偿创建订单:取消订单
                try {
                    orderService.cancelOrder((String) step.getResult());
                    log.info("补偿成功:取消订单 {}", step.getResult());
                } catch (Exception e) {
                    log.error("补偿失败:取消订单 {}", step.getResult(), e);
                    // 补偿失败的处理
                    handleCompensationFailure(sagaId, step, e);
                }
            } else if ("DeductStock".equals(step.getStepName())) {
                // 补偿扣库存:恢复库存
                try {
                    inventoryService.restoreStock(step.getParams());
                    log.info("补偿成功:恢复库存");
                } catch (Exception e) {
                    log.error("补偿失败:恢复库存", e);
                    handleCompensationFailure(sagaId, step, e);
                }
            }
        }
    }
}

【架构权衡】 Saga 的核心问题是"缺乏隔离性"。如果 Saga 执行到一半,用户查询订单状态,会看到"部分完成"的状态(TCC 通过 Try 预留解决了这个问题)。

三、工程代价评估

维度TCCSaga
实现复杂度
链路长度
资源隔离
补偿复杂度
适用场景强一致性要求长链路业务

四、落地 Checklist

  • 链路设计:明确每个服务的正向和补偿操作
  • 幂等设计:每个操作都要幂等
  • 补偿日志:持久化 Saga 执行状态
  • 补偿策略:设计补偿的执行顺序
  • 异常处理:处理补偿失败的情况
  • 监控告警:监控 Saga 的执行状态