【架构实战】分布式事务Saga模式:长事务的优雅解决方案

一、一个跨5个服务的订单流程跑了40分钟

2020年,我们的电商系统订单流程涉及5个服务:订单、库存、优惠券、积分、物流。

最初用的是TCC模式,每个服务都要实现try/confirm/cancel三个接口。开发量巨大,而且每次新增一个步骤,所有相关的补偿逻辑都要改。

有一次订单流程执行到一半,积分服务超时了,补偿逻辑又触发了库存服务的bug,导致库存数据错乱。排查了3天。

后来我们改用Saga模式,事务编排变得清晰很多,补偿逻辑也更容易维护。


二、Saga模式概述

2.1 什么是Saga

复制代码
Saga模式:

将一个长事务拆分成多个本地事务,每个本地事务完成后,
通过消息或事件触发下一个本地事务。

如果某个步骤失败,则反向执行之前所有步骤的补偿操作。

正向执行:T1 → T2 → T3 → T4 → 完成
补偿回滚:T1 → T2 → T3(失败) → C2 → C1

vs TCC:
- Saga:只有正向操作和补偿操作,没有try阶段
- TCC:有try/confirm/cancel三个阶段
- Saga更适合长事务,TCC更适合短事务

2.2 两种编排方式

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                  Saga编排方式                                     │
│                                                                  │
│  1. 编排式(Choreography)                                      │
│     - 每个服务监听事件,自己决定下一步                            │
│     - 去中心化,没有协调者                                       │
│     - 适合简单的Saga                                             │
│                                                                  │
│  2. 协调式(Orchestration)                                     │
│     - 有一个协调者(Saga Manager)                              │
│     - 协调者决定下一步执行什么                                    │
│     - 中心化,逻辑清晰                                           │
│     - 适合复杂的Saga                                             │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

三、编排式Saga实现

3.1 事件驱动

java 复制代码
/**
 * 订单创建Saga(编排式)
 */
@Service
@Slf4j
public class OrderCreateSagaChoreography {
    
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    /**
     * 启动Saga
     */
    public void startSaga(CreateOrderRequest request) {
        // Step 1: 创建订单
        Order order = createOrder(request);
        
        // 发布事件:订单已创建
        eventPublisher.publishEvent(new OrderCreatedEvent(order));
    }
    
    // ========= 库存服务 =========
    
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        try {
            // Step 2: 扣减库存
            inventoryService.deduct(event.getOrder());
            
            // 发布事件:库存已扣减
            eventPublisher.publishEvent(new InventoryDeductedEvent(event.getOrder()));
            
        } catch (Exception e) {
            // 补偿:取消订单
            orderService.cancelOrder(event.getOrder().getId());
            log.error("库存扣减失败,补偿取消订单", e);
        }
    }
    
    // ========= 优惠券服务 =========
    
    @EventListener
    public void onInventoryDeducted(InventoryDeductedEvent event) {
        try {
            // Step 3: 使用优惠券
            if (event.getOrder().getCouponId() != null) {
                couponService.useCoupon(event.getOrder().getUserId(), 
                    event.getOrder().getCouponId());
            }
            
            // 发布事件:优惠券已使用
            eventPublisher.publishEvent(new CouponUsedEvent(event.getOrder()));
            
        } catch (Exception e) {
            // 补偿:恢复库存
            inventoryService.restore(event.getOrder());
            // 补偿:取消订单
            orderService.cancelOrder(event.getOrder().getId());
            log.error("优惠券使用失败,补偿回滚", e);
        }
    }
    
    // ========= 积分服务 =========
    
    @EventListener
    public void onCouponUsed(CouponUsedEvent event) {
        try {
            // Step 4: 扣减积分
            if (event.getOrder().getPoints() > 0) {
                pointsService.deduct(event.getOrder().getUserId(), 
                    event.getOrder().getPoints());
            }
            
            // 发布事件:积分已扣减
            eventPublisher.publishEvent(new PointsDeductedEvent(event.getOrder()));
            
        } catch (Exception e) {
            // 补偿:恢复优惠券
            if (event.getOrder().getCouponId() != null) {
                couponService.restoreCoupon(event.getOrder().getUserId(), 
                    event.getOrder().getCouponId());
            }
            // 补偿:恢复库存
            inventoryService.restore(event.getOrder());
            // 补偿:取消订单
            orderService.cancelOrder(event.getOrder().getId());
            log.error("积分扣减失败,补偿回滚", e);
        }
    }
}

四、协调式Saga实现

4.1 Saga定义

java 复制代码
/**
 * Saga定义
 */
@Data
@Builder
public class SagaDefinition {
    
    private String sagaName;
    private List<SagaStep> steps;
    
    @Data
    @Builder
    public static class SagaStep {
        private String name;
        private String service;
        private String action;
        private String compensateAction;
        private Map<String, Object> params;
    }
}

/**
 * 订单创建Saga定义
 */
@Component
public class OrderCreateSagaDefinition {
    
    public SagaDefinition getSagaDefinition() {
        return SagaDefinition.builder()
            .sagaName("order-create")
            .steps(Arrays.asList(
                SagaStep.builder()
                    .name("create-order")
                    .service("order-service")
                    .action("create")
                    .compensateAction("cancel")
                    .build(),
                SagaStep.builder()
                    .name("deduct-inventory")
                    .service("inventory-service")
                    .action("deduct")
                    .compensateAction("restore")
                    .build(),
                SagaStep.builder()
                    .name("use-coupon")
                    .service("coupon-service")
                    .action("use")
                    .compensateAction("restore")
                    .build(),
                SagaStep.builder()
                    .name("deduct-points")
                    .service("points-service")
                    .action("deduct")
                    .compensateAction("restore")
                    .build(),
                SagaStep.builder()
                    .name("create-shipment")
                    .service("logistics-service")
                    .action("create")
                    .compensateAction("cancel")
                    .build()
            ))
            .build();
    }
}

4.2 Saga引擎

java 复制代码
/**
 * Saga引擎
 */
@Service
@Slf4j
public class SagaEngine {
    
    @Autowired
    private SagaInstanceRepository sagaRepository;
    
    @Autowired
    private ServiceInvoker serviceInvoker;
    
    /**
     * 执行Saga
     */
    public SagaResult execute(SagaDefinition definition, Map<String, Object> context) {
        String sagaId = UUID.randomUUID().toString();
        
        // 创建Saga实例
        SagaInstance instance = SagaInstance.builder()
            .sagaId(sagaId)
            .sagaName(definition.getSagaName())
            .status(SagaStatus.RUNNING)
            .currentStep(0)
            .context(context)
            .startTime(LocalDateTime.now())
            .build();
        
        sagaRepository.save(instance);
        
        // 逐步执行
        try {
            for (int i = 0; i < definition.getSteps().size(); i++) {
                SagaStep step = definition.getSteps().get(i);
                
                log.info("Saga执行步骤: sagaId={}, step={}/{}", 
                    sagaId, i + 1, definition.getSteps().size());
                
                // 执行步骤
                Object result = serviceInvoker.invoke(
                    step.getService(), 
                    step.getAction(), 
                    context
                );
                
                // 更新上下文
                context.put(step.getName() + "Result", result);
                
                // 更新实例状态
                instance.setCurrentStep(i + 1);
                instance.setContext(context);
                sagaRepository.update(instance);
            }
            
            // Saga完成
            instance.setStatus(SagaStatus.COMPLETED);
            instance.setEndTime(LocalDateTime.now());
            sagaRepository.update(instance);
            
            log.info("Saga执行完成: sagaId={}", sagaId);
            return SagaResult.success(sagaId);
            
        } catch (Exception e) {
            log.error("Saga执行失败: sagaId={}, step={}", 
                sagaId, instance.getCurrentStep(), e);
            
            // 补偿回滚
            compensate(definition, instance);
            
            return SagaResult.failure(sagaId, e.getMessage());
        }
    }
    
    /**
     * 补偿回滚
     */
    private void compensate(SagaDefinition definition, SagaInstance instance) {
        instance.setStatus(SagaStatus.COMPENSATING);
        sagaRepository.update(instance);
        
        int currentStep = instance.getCurrentStep();
        
        // 从当前步骤开始,反向执行补偿
        for (int i = currentStep - 1; i >= 0; i--) {
            SagaStep step = definition.getSteps().get(i);
            
            try {
                log.info("Saga补偿步骤: sagaId={}, step={}", instance.getSagaId(), step.getName());
                
                serviceInvoker.invoke(
                    step.getService(), 
                    step.getCompensateAction(), 
                    instance.getContext()
                );
                
            } catch (Exception e) {
                log.error("Saga补偿失败: sagaId={}, step={}", 
                    instance.getSagaId(), step.getName(), e);
                // 记录补偿失败,需要人工介入
            }
        }
        
        instance.setStatus(SagaStatus.COMPENSATED);
        instance.setEndTime(LocalDateTime.now());
        sagaRepository.update(instance);
    }
}

五、Seata Saga模式

5.1 Seata Saga状态机

json 复制代码
{
  "Name": "order-create-saga",
  "Comment": "订单创建Saga",
  "StartState": "CreateOrder",
  "States": {
    "CreateOrder": {
      "Type": "ServiceTask",
      "ServiceName": "orderService",
      "ServiceMethod": "create",
      "CompensateState": "CancelOrder",
      "Next": "DeductInventory",
      "Input": ["$.orderId", "$.userId", "$.items"],
      "Output": {"orderId": "$.orderId"},
      "Status": {"#root.success": "SU", "#root.fail": "FA"}
    },
    "DeductInventory": {
      "Type": "ServiceTask",
      "ServiceName": "inventoryService",
      "ServiceMethod": "deduct",
      "CompensateState": "RestoreInventory",
      "Next": "UseCoupon",
      "Input": ["$.orderId", "$.items"],
      "Catch": [{"Exceptions": ["java.lang.Exception"], "Next": "CompensationTrigger"}]
    },
    "UseCoupon": {
      "Type": "ServiceTask",
      "ServiceName": "couponService",
      "ServiceMethod": "use",
      "CompensateState": "RestoreCoupon",
      "Next": "Succeed",
      "Input": ["$.userId", "$.couponId"]
    },
    "CancelOrder": {
      "Type": "ServiceTask",
      "ServiceName": "orderService",
      "ServiceMethod": "cancel",
      "Input": ["$.orderId"]
    },
    "RestoreInventory": {
      "Type": "ServiceTask",
      "ServiceName": "inventoryService",
      "ServiceMethod": "restore",
      "Input": ["$.orderId", "$.items"]
    },
    "RestoreCoupon": {
      "Type": "ServiceTask",
      "ServiceName": "couponService",
      "ServiceMethod": "restore",
      "Input": ["$.userId", "$.couponId"]
    },
    "CompensationTrigger": {
      "Type": "CompensationTrigger",
      "Next": "Fail"
    },
    "Succeed": {"Type": "Succeed"},
    "Fail": {"Type": "Fail"}
  }
}

六、踩坑实录

坑1:补偿操作不是幂等的

补偿操作被执行了两次,导致数据错误。

解决:补偿操作必须幂等,使用唯一标识防止重复执行。

坑2:缺少隔离性

两个Saga同时操作同一资源,导致数据不一致。

解决:使用悲观锁或乐观锁,保证资源互斥。

坑3:补偿失败

补偿操作也失败了,数据处于不一致状态。

解决:记录补偿失败的任务,人工介入处理。

坑4:Saga太长

一个Saga有10个步骤,任何一步失败都要补偿9步。

解决:拆分成多个小Saga,每个Saga不超过5步。

坑5:调试困难

Saga执行到一半失败,不知道当前状态。

解决:记录每一步的执行状态,提供可视化界面。


七、总结

Saga模式选型:

场景 推荐
简单流程(3步以内) 编排式
复杂流程 协调式
需要可视化 Seata Saga
超长事务 Saga > TCC

最佳实践:

  1. 补偿操作必须幂等
  2. 控制Saga的步骤数量
  3. 做好补偿失败的处理
  4. 记录每一步的状态
  5. 提供可视化监控

血的教训:

Saga不是银弹。它解决了长事务的问题,但引入了补偿的复杂度。在决定用Saga之前,先想想能不能用更简单的方案。

思考题: 你的系统有没有跨服务的事务?用的什么方案?


个人观点,仅供参考

相关推荐
XWalnut1 小时前
Zookeeper入门
分布式·zookeeper
水木流年追梦2 小时前
大模型入门-大模型优化方法12-YaRN 长文本外推技术
人工智能·分布式·算法·正则表达式·prompt
ting94520002 小时前
Minimi 深度技术剖析:macOS 端侧全量上下文采集与 Claude 本地 RAG 联动架构详解
macos·架构·策略模式
龙佚3 小时前
移动端优化:应对移动设备的挑战
架构
原来是猿3 小时前
Docker 【 技术架构(1)】
docker·容器·架构
咖啡星人k4 小时前
MonkeyCode 网络架构:WebSocket、SSE与实时协作的技术选型
网络·websocket·架构·monkeycode
梦想的旅途24 小时前
企业微信API实现外部群消息异步推送的技术架构与实践
mysql·架构·企业微信
Algorithm_Engineer_5 小时前
如何利用Pycharm进行分布式的Debug训练
ide·分布式·pycharm
暗冰ཏོ5 小时前
Flutter 从入门到项目实战:Dart 基础、跨平台开发、App 架构与上线发布完整指南
flutter·架构·app·安卓·应用开发