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

发布时间:2026/6/4 17:23:45

【架构实战】分布式事务Saga模式:长事务的优雅解决方案 一、一个跨5个服务的订单流程跑了40分钟2020年我们的电商系统订单流程涉及5个服务订单、库存、优惠券、积分、物流。最初用的是TCC模式每个服务都要实现try/confirm/cancel三个接口。开发量巨大而且每次新增一个步骤所有相关的补偿逻辑都要改。有一次订单流程执行到一半积分服务超时了补偿逻辑又触发了库存服务的bug导致库存数据错乱。排查了3天。后来我们改用Saga模式事务编排变得清晰很多补偿逻辑也更容易维护。二、Saga模式概述2.1 什么是SagaSaga模式 将一个长事务拆分成多个本地事务每个本地事务完成后 通过消息或事件触发下一个本地事务。 如果某个步骤失败则反向执行之前所有步骤的补偿操作。 正向执行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 事件驱动/** * 订单创建Saga编排式 */ServiceSlf4jpublicclassOrderCreateSagaChoreography{AutowiredprivateApplicationEventPublishereventPublisher;/** * 启动Saga */publicvoidstartSaga(CreateOrderRequestrequest){// Step 1: 创建订单OrderordercreateOrder(request);// 发布事件订单已创建eventPublisher.publishEvent(newOrderCreatedEvent(order));}// 库存服务 EventListenerpublicvoidonOrderCreated(OrderCreatedEventevent){try{// Step 2: 扣减库存inventoryService.deduct(event.getOrder());// 发布事件库存已扣减eventPublisher.publishEvent(newInventoryDeductedEvent(event.getOrder()));}catch(Exceptione){// 补偿取消订单orderService.cancelOrder(event.getOrder().getId());log.error(库存扣减失败补偿取消订单,e);}}// 优惠券服务 EventListenerpublicvoidonInventoryDeducted(InventoryDeductedEventevent){try{// Step 3: 使用优惠券if(event.getOrder().getCouponId()!null){couponService.useCoupon(event.getOrder().getUserId(),event.getOrder().getCouponId());}// 发布事件优惠券已使用eventPublisher.publishEvent(newCouponUsedEvent(event.getOrder()));}catch(Exceptione){// 补偿恢复库存inventoryService.restore(event.getOrder());// 补偿取消订单orderService.cancelOrder(event.getOrder().getId());log.error(优惠券使用失败补偿回滚,e);}}// 积分服务 EventListenerpublicvoidonCouponUsed(CouponUsedEventevent){try{// Step 4: 扣减积分if(event.getOrder().getPoints()0){pointsService.deduct(event.getOrder().getUserId(),event.getOrder().getPoints());}// 发布事件积分已扣减eventPublisher.publishEvent(newPointsDeductedEvent(event.getOrder()));}catch(Exceptione){// 补偿恢复优惠券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定义/** * Saga定义 */DataBuilderpublicclassSagaDefinition{privateStringsagaName;privateListSagaStepsteps;DataBuilderpublicstaticclassSagaStep{privateStringname;privateStringservice;privateStringaction;privateStringcompensateAction;privateMapString,Objectparams;}}/** * 订单创建Saga定义 */ComponentpublicclassOrderCreateSagaDefinition{publicSagaDefinitiongetSagaDefinition(){returnSagaDefinition.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引擎/** * Saga引擎 */ServiceSlf4jpublicclassSagaEngine{AutowiredprivateSagaInstanceRepositorysagaRepository;AutowiredprivateServiceInvokerserviceInvoker;/** * 执行Saga */publicSagaResultexecute(SagaDefinitiondefinition,MapString,Objectcontext){StringsagaIdUUID.randomUUID().toString();// 创建Saga实例SagaInstanceinstanceSagaInstance.builder().sagaId(sagaId).sagaName(definition.getSagaName()).status(SagaStatus.RUNNING).currentStep(0).context(context).startTime(LocalDateTime.now()).build();sagaRepository.save(instance);// 逐步执行try{for(inti0;idefinition.getSteps().size();i){SagaStepstepdefinition.getSteps().get(i);log.info(Saga执行步骤: sagaId{}, step{}/{},sagaId,i1,definition.getSteps().size());// 执行步骤ObjectresultserviceInvoker.invoke(step.getService(),step.getAction(),context);// 更新上下文context.put(step.getName()Result,result);// 更新实例状态instance.setCurrentStep(i1);instance.setContext(context);sagaRepository.update(instance);}// Saga完成instance.setStatus(SagaStatus.COMPLETED);instance.setEndTime(LocalDateTime.now());sagaRepository.update(instance);log.info(Saga执行完成: sagaId{},sagaId);returnSagaResult.success(sagaId);}catch(Exceptione){log.error(Saga执行失败: sagaId{}, step{},sagaId,instance.getCurrentStep(),e);// 补偿回滚compensate(definition,instance);returnSagaResult.failure(sagaId,e.getMessage());}}/** * 补偿回滚 */privatevoidcompensate(SagaDefinitiondefinition,SagaInstanceinstance){instance.setStatus(SagaStatus.COMPENSATING);sagaRepository.update(instance);intcurrentStepinstance.getCurrentStep();// 从当前步骤开始反向执行补偿for(inticurrentStep-1;i0;i--){SagaStepstepdefinition.getSteps().get(i);try{log.info(Saga补偿步骤: sagaId{}, step{},instance.getSagaId(),step.getName());serviceInvoker.invoke(step.getService(),step.getCompensateAction(),instance.getContext());}catch(Exceptione){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状态机{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补偿失败补偿操作也失败了数据处于不一致状态。解决记录补偿失败的任务人工介入处理。坑4Saga太长一个Saga有10个步骤任何一步失败都要补偿9步。解决拆分成多个小Saga每个Saga不超过5步。坑5调试困难Saga执行到一半失败不知道当前状态。解决记录每一步的执行状态提供可视化界面。七、总结Saga模式选型场景推荐简单流程3步以内编排式复杂流程协调式需要可视化Seata Saga超长事务Saga TCC最佳实践补偿操作必须幂等控制Saga的步骤数量做好补偿失败的处理记录每一步的状态提供可视化监控血的教训Saga不是银弹。它解决了长事务的问题但引入了补偿的复杂度。在决定用Saga之前先想想能不能用更简单的方案。思考题你的系统有没有跨服务的事务用的什么方案个人观点仅供参考

相关新闻