第一种:
{
"nodes": [
{
"type": "node",
"size": "110*48",
"shape": "flow-capsule",
"color": "red",
"label": "Compensation\nTrigger",
"stateId": "CompensationTrigger1",
"stateType": "CompensationTrigger",
"x": 528.375,
"y": 284.5,
"id": "78329956"
},
{
"type": "node",
"size": "72*72",
"shape": "flow-circle",
"color": "#FA8C16",
"label": "Start",
"stateId": "Start1",
"stateType": "Start",
"stateProps": {
"StateMachine": {
"Name": "transferBySaga",
"Comment": "a simple transfer demo by saga",
"Version": "0.0.1"
}
},
"x": 354.375,
"y": 56,
"id": "e3c845c6"
},
{
"type": "node",
"size": "110*48",
"shape": "flow-rect",
"color": "#1890FF",
"label": "amountMinus",
"stateId": "amountMinus",
"stateType": "ServiceTask",
"stateProps": {
"ServiceName": "firstSagaAction",
"ServiceMethod": "amountMinus",
"Input": [
"$.[_business_key_]",
"$.[from]",
"$.[amount]",
{
"sleepTime": "$.[extParams][amountMinusSleepTime]",
"throwException": "$.[extParams][amountMinusThrowException]"
}
],
"Output": {
"amountMinusResult": "$.#root"
},
"Status": {
"$Exception{java.lang.Throwable}": "UN",
"#root == true": "SU",
"#root != true": "FA"
},
"Retry": [
{
"IntervalSeconds": 1,
"MaxAttempts": 3,
"BackoffRate": 1.5
}
]
},
"x": 354.375,
"y": 165,
"id": "58a2086e"
},
{
"type": "node",
"size": "80*72",
"shape": "flow-rhombus",
"color": "#13C2C2",
"label": "Choice",
"stateId": "Choice1",
"stateType": "Choice",
"x": 354.375,
"y": 284.5,
"id": "eb17480a"
},
{
"type": "node",
"size": "110*48",
"shape": "flow-rect",
"color": "#1890FF",
"label": "amountAdd",
"stateId": "amountAdd",
"stateType": "ServiceTask",
"stateProps": {
"ServiceName": "secondSagaAction",
"ServiceMethod": "amountAdd",
"Input": [
"$.[_business_key_]",
"$.[to]",
"$.[amount]",
{
"sleepTime": "$.[extParams][amountAddSleepTime]",
"throwException": "$.[extParams][amountAddThrowException]"
}
],
"Output": {
"amountAddResult": "$.#root"
},
"Status": {
"$Exception{java.lang.Throwable}": "UN",
"#root == true": "SU",
"#root != true": "FA"
},
"Retry": [
{
"IntervalSeconds": 1,
"MaxAttempts": 3,
"BackoffRate": 1.5
}
]
},
"x": 354.375,
"y": 408.5,
"id": "a307dd5b"
},
{
"type": "node",
"size": "80*72",
"shape": "flow-rhombus",
"color": "#13C2C2",
"label": "Choice",
"stateId": "Choice2",
"stateType": "Choice",
"x": 354.375,
"y": 530.5,
"id": "3a1ccb40"
},
{
"type": "node",
"size": "72*72",
"shape": "flow-circle",
"color": "#05A465",
"label": "Succeed",
"stateId": "Succeed1",
"stateType": "Succeed",
"x": 354.375,
"y": 649.5,
"id": "c55b2604"
},
{
"type": "node",
"size": "110*48",
"shape": "flow-capsule",
"color": "#722ED1",
"label": "compensateMinus",
"stateId": "compensateMinus",
"stateType": "Compensation",
"stateProps": {
"ServiceName": "firstSagaAction",
"ServiceMethod": "compensateAmountMinus",
"Input": [
"$.[_business_key_]",
"$.[from]"
],
"Output": {
"compensateAmountMinusResult": "$.#root"
},
"Status": {
"#root == true": "SU",
"#root != true": "FA"
},
"Retry": [
{
"IntervalSeconds": 1,
"MaxAttempts": 3,
"BackoffRate": 1.5
}
]
},
"x": 176.375,
"y": 99,
"id": "07a2561b"
},
{
"type": "node",
"size": "110*48",
"shape": "flow-capsule",
"color": "#722ED1",
"label": "compensateAdd",
"stateId": "compensateAdd",
"stateType": "Compensation",
"stateProps": {
"ServiceName": "secondSagaAction",
"ServiceMethod": "compensateAmountAdd",
"Input": [
"$.[_business_key_]",
"$.[to]"
],
"Output": {
"compensateAmountAddResult": "$.#root"
},
"Status": {
"#root == true": "SU",
"#root != true": "FA"
},
"Retry": [
{
"IntervalSeconds": 1,
"MaxAttempts": 3,
"BackoffRate": 1.5
}
]
},
"x": 177.375,
"y": 351,
"id": "dac44a1a"
},
{
"type": "node",
"size": "39*39",
"shape": "flow-circle",
"color": "red",
"label": "Catch",
"stateId": "Catch1",
"stateType": "Catch",
"x": 412.375,
"y": 195,
"id": "c2f35514"
},
{
"type": "node",
"size": "39*39",
"shape": "flow-circle",
"color": "red",
"label": "Catch",
"stateId": "Catch2",
"stateType": "Catch",
"x": 415.375,
"y": 438.5,
"id": "93b63a93"
},
{
"type": "node",
"size": "72*72",
"shape": "flow-circle",
"color": "red",
"label": "Fail",
"stateId": "Fail1",
"stateType": "Fail",
"stateProps": {
"ErrorCode": "TRANSFER_FAILED",
"Message": "transfer failed, and rollbacked"
},
"x": 603.375,
"y": 649.5,
"id": "53d0b2c1"
}
],
"edges": [
{
"source": "c2f35514",
"sourceAnchor": 1,
"target": "78329956",
"targetAnchor": 0,
"id": "b4865277",
"stateProps": {
"Exceptions": [
"java.lang.Throwable"
]
},
"label": "",
"shape": "flow-polyline-round"
},
{
"source": "eb17480a",
"sourceAnchor": 1,
"target": "78329956",
"targetAnchor": 3,
"id": "fd4278d3",
"stateProps": {
"Expression": "[amountMinusResult] == false",
"Default": false
},
"label": "failed",
"shape": "flow-polyline-round"
},
{
"source": "93b63a93",
"sourceAnchor": 1,
"target": "78329956",
"targetAnchor": 3,
"id": "512f774a",
"stateProps": {
"Exceptions": [
"java.lang.Throwable"
]
},
"label": "",
"shape": "flow-polyline-round"
},
{
"source": "3a1ccb40",
"sourceAnchor": 1,
"target": "78329956",
"targetAnchor": 2,
"id": "7f4ba0bc",
"stateProps": {
"Expression": "[amountAddResult] == false",
"Default": false
},
"label": "failed",
"shape": "flow-polyline-round"
},
{
"source": "e3c845c6",
"sourceAnchor": 2,
"target": "58a2086e",
"targetAnchor": 0,
"id": "5234f532"
},
{
"source": "58a2086e",
"sourceAnchor": 2,
"target": "eb17480a",
"targetAnchor": 0,
"id": "f2c43c0d",
"label": "",
"shape": "flow-polyline-round"
},
{
"source": "eb17480a",
"sourceAnchor": 2,
"target": "a307dd5b",
"targetAnchor": 0,
"id": "253d337b",
"stateProps": {
"Expression": "[amountMinusResult] == true",
"Default": false
},
"label": "success",
"shape": "flow-polyline-round"
},
{
"source": "a307dd5b",
"sourceAnchor": 2,
"target": "3a1ccb40",
"targetAnchor": 0,
"id": "3ac7b857",
"label": "",
"shape": "flow-polyline-round"
},
{
"source": "3a1ccb40",
"sourceAnchor": 2,
"target": "c55b2604",
"targetAnchor": 0,
"id": "8655605c",
"stateProps": {
"Expression": "[amountAddResult] == true",
"Default": false
},
"label": "success",
"shape": "flow-polyline-round"
},
{
"source": "78329956",
"sourceAnchor": 1,
"target": "53d0b2c1",
"targetAnchor": 0,
"id": "359090fb",
"label": "",
"shape": "flow-polyline-round"
},
{
"source": "58a2086e",
"sourceAnchor": 3,
"target": "07a2561b",
"targetAnchor": 2,
"id": "ae7a01c9",
"shape": "flow-polyline-round",
"style": {
"lineDash": "4",
"type": "Compensation",
"endArrow": false
}
},
{
"source": "a307dd5b",
"sourceAnchor": 3,
"target": "dac44a1a",
"targetAnchor": 2,
"id": "7e6157fd",
"shape": "flow-polyline-round",
"style": {
"lineDash": "4",
"type": "Compensation",
"endArrow": false
}
}
]
}
Saga 状态机的 JSON 定义,描述了一个基于 Saga 模式的转账流程(transferBySaga),包含了事务步骤、补偿操作、分支判断等节点,完整呈现了 Saga 事务的编排逻辑。以下是详细解析:
一、整体流程:转账 Saga 事务的执行逻辑
这个 Saga 状态机定义了一个 “转账” 场景:从账户 A 扣钱(amountMinus)→ 向账户 B 加钱(amountAdd),若任何步骤失败,则触发对应的补偿操作(恢复账户 A 的钱、扣减账户 B 的钱),最终返回成功或失败。
二、核心节点解析
1. 开始与结束节点
-
Start1(开始节点):状态机入口,定义了 Saga 事务的基本信息:json
"stateProps": { "StateMachine": { "Name": "transferBySaga", // 事务名称:转账 Saga "Comment": "a simple transfer demo by saga", // 描述:简单转账示例 "Version": "0.0.1" // 版本号 } } -
Succeed1(成功节点):所有步骤执行成功后进入此节点,事务结束。 -
Fail1(失败节点):任何步骤失败且补偿完成后进入此节点,返回错误信息(TRANSFER_FAILED)。
2. 业务步骤节点(ServiceTask)
这是 Saga 事务的核心执行步骤,对应具体的业务操作:
-
amountMinus(扣钱操作):调用firstSagaAction组件的amountMinus方法(即你之前提供的FirstSagaActionImpl中的扣钱逻辑):- 输入参数:业务唯一标识(
_business_key_)、扣款账户(from)、金额(amount)、扩展参数(如超时配置)。 - 输出参数:将扣钱结果存入
amountMinusResult变量,供后续判断。 - 状态判断:
- 抛出异常 → 状态
UN(未知); - 返回
true→ 状态SU(成功); - 返回
false→ 状态FA(失败)。
- 抛出异常 → 状态
- 重试策略:失败后重试 3 次,间隔 1 秒,重试间隔倍率 1.5(即 1s → 1.5s → 2.25s)。
- 输入参数:业务唯一标识(
-
amountAdd(加钱操作):调用secondSagaAction组件的amountAdd方法(向目标账户加钱),配置与amountMinus类似,输出结果存入amountAddResult。
3. 分支判断节点(Choice)
根据步骤执行结果决定后续流程:
-
Choice1:判断amountMinus的执行结果(amountMinusResult):- 若
true(成功)→ 进入amountAdd步骤; - 若
false(失败)→ 触发CompensationTrigger(补偿触发器)。
- 若
-
Choice2:判断amountAdd的执行结果(amountAddResult):- 若
true(成功)→ 进入Succeed1(事务成功); - 若
false(失败)→ 触发CompensationTrigger。
- 若
4. 补偿相关节点
-
CompensationTrigger1(补偿触发器):接收失败信号后,触发逆序补偿操作(先补偿amountAdd,再补偿amountMinus)。 -
compensateMinus(扣钱的补偿):调用firstSagaAction的compensateAmountMinus方法(恢复账户 A 的金额,对应你提供的补偿逻辑),输入参数为_business_key_和from(扣款账户)。 -
compensateAdd(加钱的补偿):调用secondSagaAction的compensateAmountAdd方法(扣减账户 B 多加上的金额)。 -
Catch1/Catch2(异常捕获):捕获amountMinus/amountAdd执行中的异常(如抛出Throwable),将异常信号传递给CompensationTrigger1,触发补偿。
5. 边(Edges):节点间的流转规则
- 实线箭头:正常流程(如
Start1→amountMinus→Choice1)。 - 虚线箭头:补偿关联(如
amountMinus关联compensateMinus,表示若amountMinus需补偿,调用此补偿方法)。 - 条件判断:如
Choice1到amountAdd的边标记success(条件amountMinusResult == true),到CompensationTrigger1的边标记failed(条件amountMinusResult == false)。
三、执行流程示例
1. 正常成功流程
Start1 → amountMinus(成功)→ Choice1(走 success 分支)→ amountAdd(成功)→ Choice2(走 success 分支)→ Succeed1。
2. amountAdd 失败流程
Start1 → amountMinus(成功)→ Choice1 → amountAdd(失败)→ Choice2(走 failed 分支)→ CompensationTrigger1 → 触发 compensateAdd(补偿加钱)→ 触发 compensateMinus(补偿扣钱)→ Fail1。
3. amountMinus 抛出异常流程
Start1 → amountMinus(抛异常)→ Catch1 → CompensationTrigger1 → 触发 compensateMinus(补偿扣钱)→ Fail1。
四、与代码的关联
- 此 JSON 定义的
ServiceName(如firstSagaAction)对应 Spring 容器中的 Bean 名称,ServiceMethod(如amountMinus)对应 Bean 中的方法,与你之前提供的FirstSagaActionImpl代码完全对应。 - 补偿方法
compensateAmountMinus也通过Compensation节点关联,实现了 “执行步骤与补偿步骤” 的绑定。
第二种:
你提到的 Seata 中通过 CompensateState 进行回滚的机制,确实体现了 Seata Saga 与你提供的 JSON 配置在 补偿触发逻辑 上的核心差异。这种差异源于 Seata 对 Saga 模式的 “状态驱动补偿” 设计,与你提供的 “结果判断驱动补偿” 形成对比,具体区别如下:
一、Seata Saga 中 CompensateState 的作用
在 Seata Saga 模式中,每个分支事务(ServiceTask)的执行状态会被框架记录在 SagaStateMachineInstance 中,其中 CompensateState 是核心状态字段,用于标识当前步骤是否需要补偿、正在补偿或已补偿。常见状态包括:
NONE:未执行或无需补偿;STARTED:补偿已开始;SUCCEEDED:补偿成功;FAILED:补偿失败。
补偿触发逻辑:Seata Saga 并不依赖业务返回结果(如 SU/FA)来判断是否补偿,而是通过 全局事务状态 + 分支执行状态 自动驱动补偿:
- 当任意分支事务执行抛出异常(未正常完成),全局事务状态标记为
ROLLBACKING; - 框架根据
SagaStateMachineInstance记录的分支执行顺序,逆序查询所有已成功执行的分支(CompensateState=NONE且执行成功); - 对这些分支依次触发补偿,并更新其
CompensateState(如STARTED→SUCCEEDED)。
简言之,Seata 通过 “状态追踪” 决定补偿对象,而非业务结果判断。
二、你提供的 JSON 配置:基于业务结果(SU/FA)触发补偿
你提供的 JSON 配置中,补偿触发依赖 显式的业务结果判断(如 [amountMinusResult] == false),这是典型的 “规则驱动补偿”:
- 每个
ServiceTask执行后,通过Status配置将业务结果映射为SU(成功)、FA(失败)、UN(未知); Choice节点通过判断这些状态(如amountMinusResult == false)决定是否走向CompensationTrigger;CompensationTrigger再按预设的补偿关联(虚线箭头)触发逆序补偿。
这种方式中,补偿的触发完全依赖业务返回的 “成功 / 失败” 结果,框架不主动追踪分支状态,而是按配置的规则执行。
三、核心差异对比
| 维度 | Seata Saga(CompensateState 驱动) | 你提供的 JSON 配置(业务结果驱动) |
|---|---|---|
| 补偿触发依据 | 框架自动追踪分支执行状态(CompensateState),无需业务判断 | 依赖业务返回结果(SU/FA),通过 Choice 节点显式配置触发规则 |
| 状态管理 | 框架维护全局事务状态和分支补偿状态(持久化到数据库) | 业务需自行通过流水表记录状态(如你提供的 AccountTransaction) |
| 业务侵入性 | 低(无需业务代码返回结果标识,异常即可触发补偿) | 高(需业务代码返回 true/false,并在配置中定义判断规则) |
| 灵活性 | 适合简单线性流程(自动逆序补偿) | 适合复杂流程(可自定义补偿触发条件,如部分失败不补偿) |
四、为什么 Seata 选择 CompensateState 机制?
Seata 作为分布式事务框架,设计目标是 “简化开发者工作量”:
- 开发者无需手动定义补偿触发规则(如
Choice节点的success/failed判断),只需保证业务方法抛出异常(表示失败); - 框架通过状态追踪自动完成 “哪些步骤需要补偿”“按什么顺序补偿”,减少人为配置错误;
- 与 Seata 其他模式(如 AT)的状态管理逻辑统一,便于全局事务监控和恢复。
总结
Seata Saga 中通过 CompensateState 驱动回滚,与你提供的 JSON 配置中通过 SU/FA 状态判断补偿,本质是 “框架自动管理” 与 “人工规则配置” 的区别:
- Seata 更注重 “无侵入”,通过状态追踪简化开发;
- 你提供的 JSON 配置更注重 “灵活性”,适合复杂流程的定制化补偿规则。
两种方式各有适用场景,但核心都遵循 Saga 模式 “正向执行、反向补偿” 的基本原则。

被折叠的 条评论
为什么被折叠?



