ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SeataSaga状态机编排实战博客-2026-09-09

SeataSaga状态机编排实战博客-2026-09-09 Seata Saga 状态机编排实战用补偿逻辑把一句下单做成跨服务长事务阅读对象后端开发 / 技术负责人/感兴趣的读者主题Seata Saga 在真实项目中的应用——从为什么需要 Saga到状态机 JSON 怎么写、补偿怎么设计、踩哪些坑技术栈Java 8 / Spring Boot / Seata 1.4.1Saga StateMachine说明为保护业务文中业务均为基础示意、代码为伪代码机制与实现思路取自真实实践。0.写在开始一笔交易横跨订单/库存/卡券/权益/积分多个服务、多个库靠本地事务管不了 → 用Saga长事务 逆序补偿而不是 2PC/XA。Seata Saga 把下单做成一张状态机图StateMachine流程定义在JSON里改流程不改码每个可回滚步骤配一个补偿节点CompensateState。三个必须掌握的内核CompensateState补账、UN/FA控制可重试→补偿 / 不可恢复→失败、Catch→CompensationTrigger进入补偿汇总两个工程要点分布式锁防重、不落库/异步节点。坑要早知道UN/FA 写错会无限重试 or 不补偿、Catch 漏了不倒补偿、补偿要自己写幂等/空补偿/防悬挂。1. 背景一笔下单为什么几行代码就崩电商/汽车领域下单是这样的示意步骤影响失败要还原锁库存库存服务独立库解锁消费卡券卡券中心退回卡券使用权益权益服务回退权益建订单订单域删订单每个服务独立数据库、独立事务本地事务覆盖不了整个链路直接全程都写进一个大方法很容易但任何一个下游突然超时/抛错前面已扣的库存/券/权益全脏了传统方案全人工补偿 or 定时对账补差——都不可靠。于是需要自动补偿。两条路分布式事务 2PC / XA把所有资源纳入同一事务、阶段提交。太重长锁、低并发、所有参与方都要支持。Saga把长链路拆成本地事务步骤 可见的补偿步骤每个步骤本地提交失败了按逆序跑补偿。高并发友好、最终一致、各服务只管自己的补偿。这单业务明显是资源可回退、需要高并发所以选 Saga。2. 选型为什么是 Seata Saga2.1 Saga 与 2PC/XA 的对比决策表维度Seata Saga2PC / XA / Seata AT本地事务拼接事务模型长事务 逆序补偿两阶段提交资源锁无跨资源事务性能/并发各步骤本地提交高全程持锁低无协调开销一致性最终一致强一致无开发成本需要写补偿低AT 自动低但以脏账为代价适用跨多服务、可回退资源、高并发强一致、量小单服务结论下单是跨服务 可回退 高并发正好是 Saga 的主场XA 拿来做强一致但扛不住促销流量本地事务直接放弃补偿。2.2 事务模型图Saga流程补偿解锁补偿退券补偿删单失败步骤1 锁库存步骤2 消费卡券步骤3 建订单成功C1C2C3逆序补偿C3→C2→C1最终一致或显式失败2.3 依赖与配置架子dependencygroupIdio.seata/groupIdartifactIdseata-all/artifactIdversion1.4.1/version/dependencyseata:saga:corePoolSize:1maximumPoolSize:20keepAliveTime:60capacityOfQueue:300003. 核心概念StateMachine / State / CompensateState / CompensationTrigger一张图说清骨架MermaidStateMachine一张状态机 一次长事务CompensateState CA 解锁CompensateState CB 退券CompensateState CC 删单失败/Catch逆序逆序逆序State AServiceTask: 锁库存State BServiceTask: 消费卡券State CServiceTask: 建订单SucceedCACBCCCompensationTriggerStateMachineJSON 定义的一张图Name即唯一流程名。State节点ServiceTask调 Bean 方法/Choice分支/Succeed/Fail/CompensationTrigger。CompensateState某个正向步骤挂了要跑的逆向动作解锁/退券/删单。CompensationTrigger补偿汇总点触发 Saga 逆序执行所有已通过步骤的补偿。3.1 一图记忆源码视角伪代码// 引擎装配配置类StateMachineEngineenginenewProcessCtrlStateMachineEngine();engine.setStateMachineConfig(config);// config 从 classpath*:orchestration/**/*.json 加载// 封装调用带锁 判状态StateMachineInstanceinstengine.start(name,tenantId,params);if(inst.getException()!null)throwinst.getException();// 引擎内部异常if(!inst.getStatus().equals(SU))thrownewRuntimeException(execute.failed);// 状态非成功4. 落地把下单写成一张状态机 JSON4.1 流程定义伪代码saga.json 示意{ Name: placeOrderExample, StartState: LockStock, States: { LockStock: { Type: ServiceTask, ServiceName: stockProcess, ServiceMethod: lock, CompensateState: CompensateLockStock, // 锁了库存挂了必须解锁 Next: ConsumeCoupon, Input: [$.[req]], Output: {lockResult: $.#root}, Status: { #root true: SU, #root false: FA, $Exception{java.lang.Throwable}: UN // 注意可重试→UN 才有补偿不可恢复用 FA }, Retry: [{ Exceptions: [java.lang.Throwable], IntervalSeconds: 1, MaxAttempts: 3 }], Catch: [{ Exceptions: [java.lang.Throwable], Next: CompensationTrigger }] }, ConsumeCoupon: { ServiceName: couponProcess, ServiceMethod: consume, CompensateState: CompensateConsumeCoupon, Next: CreateOrder, Status: { #root true: SU, #root false: FA, $Exception{Throwable}: FA } }, CreateOrder: { ServiceName: orderProcess, ServiceMethod: create, CompensateState: CompensateCreateOrder, Next: SendMsg, Status: { #root ! null: SU, #root null: FA, $Exception{Throwable}: FA } }, SendMsg: { ServiceName: notifyProcess, ServiceMethod: send, IsAsync: true, // 异步不阻塞主链 IsPersist: false, // 不落状态机执行日志 Next: Succeed }, CompensateLockStock: { ServiceName: stockProcess, ServiceMethod: unlock }, CompensateConsumeCoupon: { ServiceName: couponProcess, ServiceMethod: rollback }, CompensateCreateOrder: { ServiceName: orderProcess, ServiceMethod: delete }, CompensationTrigger: { Type: CompensationTrigger, Next: Fail }, Succeed: { Type: Succeed }, Fail: { Type: Fail, ErrorCode: CREATE_ORDER_FAILED } } }主链标注可回退步骤 补偿步骤ServiceName.Method补偿CompensateStateLockStockstockProcess.lockCompensateLockStock → unlockConsumeCouponcouponProcess.consumeCompensateConsumeCoupon → rollbackCreateOrderorderProcess.createCompensateCreateOrder → deleteSendMsgnotifyProcess.send无消息类不补偿4.2 失败是怎么补齐的时序建订单消费卡券锁库存Seata Saga建订单消费卡券锁库存Seata Sagalock ✅consume ✅create ❌(抛错/Catch)CompensationTrigger逆序: 删订单(如果部分创建)逆序: 退卡券逆序: 解锁库存状态机 - Fail(记录原因)5. 与 Spring 深度集成 工程要点5.1 节点即 Bean直接注入Component(stockProcess)publicclassStockProcess{ResourceprivateStockServicestockService;// 正常注入 Feign/事务publicBooleanlock(MapString,Objectreq){...}publicBooleanunlock(MapString,Objectreq){...}// 补偿}ServiceName Spring Bean 名ServiceMethod 方法名 —— 与 LiteFlow 的组件即 Bean异曲同工但这里方法是正向/补偿成对。5.2 分布式锁 状态判定编排入口伪代码publicStateMachineInstancestartWithLock(name,tenantId,params,redisKey,expire){if(!redis.setNx(redisKey,1,expire)){thrownewBusyException(request.is.being.handled);// 幂等防重}try{StateMachineInstanceinstengine.start(name,tenantId,params);if(inst.getException()!null)throwinst.getException();if(!SU.equals(inst.getStatus()))thrownewRuntimeException(state.machine.execute.failed);returninst;}finally{redis.delete(redisKey);}}锁 key下单用业务号取消/关闭用父订单号 → 天然防重。SU才算成功否则抛异常由外层事务统一处理。5.3 局部分支子流程/回退设计说明有资源锁库存/券/权益的步骤 → 必配CompensateState只有判断/消息的步骤 →IsPersistfalse不落库发消息可IsAsynctrue异步不拖慢主链。6. 经验与坑都是实践里踩过的#坑 / 注意说明1UN vs FA 写错带补偿的步骤可重试异常若标FA→saga 不执行补偿不可恢复异常若标UN→无限重试。两个都要对2Catch 漏配CompensationTrigger靠Catch进入漏了 Catch 异常直接上抛兼职不到补偿汇总3补偿自己写、要幂等补偿方法要允许幂等调用重复退券/重复解锁不能翻车4空补偿 / 防悬挂正向没执行完、补偿已经翻的时候要防空补偿流程顶部真实 JSON 常留 TODO说明没全补5IsPersist/IsAsync 乱标消息/查询步骤标IsPersistfalse防日志膨胀需要异步的标IsAsynctrue别主链阻塞6JSON 与 target/classes 不同步运行时读的是编译副本以src/main/resources为准7流程不可见性状态机实例持久化到 DB 才有迹可查别把IsPersist全设 false8范围控制Saga 适合最终一致可接受必须强一致的点仍要配合本地事务/幂等/对账兜底改造后收益对比指标无 Saga手动补偿Seata Saga补账自动化每处失败都要人补逆序补偿自动执行可追溯散状态机实例落库可查并发吞吐受 2PC/XA 限制或裸奔各步骤本地提交高并发友好排查卡在中间全靠日志看状态机当前 State 补偿状态7. 结尾决策清单什么时候用/不用 Saga该用一笔长链路跨多个独立库/服务任一步失败要还原前面步骤资源可扣了再还库存/券/积分/权益接受最终一致需要高并发不想要 2PC 的持锁成本。别用或配合用必须强一致、量小 → 优先看 Seata AT/本地事务只有两步、可拆成一次调用 → 不值得上 Saga补偿逻辑不可行某些外部动作不可逆→ 用对账 人工补差更稳。附文中的图清单图类型位置Saga 事务模型图mermaid flowchart§2.2状态机概念图StateMachine/Compensate/Triggermermaid flowchart§3失败逆序补偿时序图mermaid sequenceDiagram§4.2本文为脱敏实战篇机制StateMachine/CompensateState/UN·FA/Catch/IsAsync为真实所写业务与代码均以示意呈现。欢迎结合自身项目替换业务字段后使用。
返回列表