Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >5 张图带你了解分布式事务 Saga 模式中的状态机

5 张图带你了解分布式事务 Saga 模式中的状态机

作者头像
jinjunzhu
发布于 2024-03-20 12:36:33
发布于 2024-03-20 12:36:33
73400
代码可运行
举报
文章被收录于专栏:个人开发个人开发
运行总次数:0
代码可运行

大家好,我是君哥。

状态机在我们的工作中应用非常广泛,今天聊一聊分布式事务中间件 Seata 中 Saga 模式的状态机。

1 状态机简介

状态机是一个数学模型,它将工作中的运行状态和流转规则抽象出来,可以协调相关信号来完成预先设定的操作。

下面介绍状态机中的几个概念:

  • 状态:状态机目前的状态标识;
  • 状态转移:定义状态之间的转移路由
  • 动作(Action):状态转移需要的操作;
  • 事件:要执行某个操作时的触发器或者口令。

状态机一般用在状态类型比较多(超过 3 个),分支流程比较多,初始状态经过多个流程的流转达到最终状态的场景。

2 Saga 模式

Saga 模式是分布式事务中长事务的一种解决方案,Seata 中 Saga 模式的理论基础是 Hector & Kenneth 在 1987 年发表的论文 Sagas。下图(来自官网)是 Seata 中 Saga 模型:

在 Saga 模式中,如果一部分分支事务已经提交成功,当其中一个分支事务提交失败,状态机就会触发所有提交成功的分支事务进行回滚。

分支事务中提交和回滚的逻辑需要由业务代码来实现。

3 Saga 实现

Seata 中 Saga 模式是基于状态机来实现的,使用 Saga 模式时,先画一张状态图,这个状态图定义服务调用流程,每个节点调用一个分支事务,并且每个节点需要配备一个补偿节点用于分支事务失败后的补偿动作。

以经典电商案例来讲,一个分布式事务中有三个分支事务参数者:

分支事务

动作

状态

订单服务

保存订单

保存成功、失败

账户服务

扣减金额

扣减成功、失败

库存服务

扣减库存

扣减成功、失败

在这个分布式事务中,只有订单、账户、库存这三个分支事务都提交成功,整个事务才能成功。每一个分支事务提交失败,其他执行成功的事务都需要反向补偿。如下图:

比如扣减金额这个分支事务失败了,需要反向补偿扣减金额、保存订单这两个分支事务。那 Seata 是怎么做到事件触发、状态流转和补偿操作的呢?

使用 Seata 状态机,首先需要定义一个 Json 文件,这个 Json 文件把图中的每个节点都定义成一个 State,State 的类型共有四种:

  • ServiceTask:对应分支事务的提交操作;
  • Choice:对应流程中下一个 State 的选择;
  • CompensationTrigger:触发补偿服务;
  • Succeed:成功状态,当所有分支事务都成功后才会流转到这个状态;
  • Fail:失败状态。

3.1 ServiceTask

下面我们看"保存订单"这个状态:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"SaveOrder": {
 "Type": "ServiceTask",
 "ServiceName": "orderSave",
 "ServiceMethod": "saveOrder",
 "CompensateState": "DeleteOrder",
 "Next": "ChoiceAccountState",
 "Input": [
  "$.[businessKey]",
  "$.[order]"
 ],
 "Output": {
  "SaveOrderResult": "$.#root"
 },
 "Status": {
  "#root == true": "SU",
  "#root == false": "FA",
  "$Exception{java.lang.Throwable}": "UN"
 },
 "Catch": [
  {
   "Exceptions": [
    "java.lang.Throwable"
   ],
   "Next": "CompensationTrigger"
  }
 ]
},

这个 State 的类型是 ServiceTask,上面图中的分支服务和补偿服务都是这种类型,也对应代码中的一个 Service。上面的 Json 中主要定义了三个内容:

  • 这个 state 调用的 Service 方法;
  • 提交失败后的补偿 State(CompensateState);
  • 提交成功后应该跳转的下一个 State(ChoiceAccountState)。

3.2 Choice

下面来看 ChoiceAccountState 这个状态节点,Json 文件定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"ChoiceAccountState":{
 "Type": "Choice",
 "Choices":[
  {
   "Expression":"[SaveOrderResult] == true",
   "Next":"ReduceAccount"
  }
 ],
 "Default":"Fail"
}

对应的下个节点是 ReduceAccount,如果失败就会跳转 Fail 状态。

3.3 Fail

上面 orderSave 这个状态节点如果发生异常,会跳转到 CompensationTrigger,CompensationTrigger 状态节点定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"CompensationTrigger": {
 "Type": "CompensationTrigger",
 "Next": "Fail"
}

这个节点会触发 SaveOrder 中定义的补偿服务,然后将最终状态流转到 Fail。同时我们也看到,只要到了 CompensationTrigger 这个状态节点,最终状态就会流转到 Fail。

下面我们把整个 Json 文件的定义贴出来看一下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
    "Name": "buyGoodsOnline",
    "Comment": "buy a goods on line, add order, deduct account, deduct storage ",
    "StartState": "SaveOrder",
    "Version": "0.0.1",
 #定义状态
    "States": {
        "SaveOrder": {
            "Type": "ServiceTask",
            "ServiceName": "orderSave",
            "ServiceMethod": "saveOrder",
            "CompensateState": "DeleteOrder",
            "Next": "ChoiceAccountState",
            "Input": [
                "$.[businessKey]",
                "$.[order]"
            ],
            "Output": {
                "SaveOrderResult": "$.#root"
            },
            "Status": {
                "#root == true": "SU",
                "#root == false": "FA",
                "$Exception{java.lang.Throwable}": "UN"
            },
   "Catch": [
                {
                    "Exceptions": [
                        "java.lang.Throwable"
                    ],
                    "Next": "CompensationTrigger"
                }
            ]
        },
        "ChoiceAccountState":{
            "Type": "Choice",
            "Choices":[
                {
                    "Expression":"[SaveOrderResult] == true",
                    "Next":"ReduceAccount"
                }
            ],
            "Default":"Fail"
        },
        "ReduceAccount": {
            "Type": "ServiceTask",
            "ServiceName": "accountService",
            "ServiceMethod": "decrease",
            "CompensateState": "CompensateReduceAccount",
            "Next": "ChoiceStorageState",
            "Input": [
                "$.[businessKey]",
                "$.[userId]",
                "$.[money]",
                {
                    "throwException" : "$.[mockReduceAccountFail]"
                }
            ],
            "Output": {
                "ReduceAccountResult": "$.#root"
            },
            "Status": {
                "#root == true": "SU",
                "#root == false": "FA",
                "$Exception{java.lang.Throwable}": "UN"
            },
            "Catch": [
                {
                    "Exceptions": [
                        "java.lang.Throwable"
                    ],
                    "Next": "CompensationTrigger"
                }
            ]
        },
        "ChoiceStorageState":{
            "Type": "Choice",
            "Choices":[
                {
                    "Expression":"[ReduceAccountResult] == true",
                    "Next":"ReduceStorage"
                }
            ],
            "Default":"Fail"
        },
        "ReduceStorage": {
            "Type": "ServiceTask",
            "ServiceName": "storageService",
            "ServiceMethod": "decrease",
            "CompensateState": "CompensateReduceStorage",
            "Input": [
                "$.[businessKey]",
                "$.[productId]",
                "$.[count]",
                {
                    "throwException" : "$.[mockReduceStorageFail]"
                }
            ],
            "Output": {
                "ReduceStorageResult": "$.#root"
            },
            "Status": {
                "#root == true": "SU",
                "#root == false": "FA",
                "$Exception{java.lang.Throwable}": "UN"
            },
            "Catch": [
                {
                    "Exceptions": [
                        "java.lang.Throwable"
                    ],
                    "Next": "CompensationTrigger"
                }
            ],
            "Next": "Succeed"
        },
        "DeleteOrder": {
            "Type": "ServiceTask",
            "ServiceName": "orderSave",
            "ServiceMethod": "deleteOrder",
            "Input": [
                "$.[businessKey]",
                "$.[order]"
            ]
        },
        "CompensateReduceAccount": {
            "Type": "ServiceTask",
            "ServiceName": "accountService",
            "ServiceMethod": "compensateDecrease",
            "Input": [
                "$.[businessKey]",
                "$.[userId]",
                "$.[money]"
            ]
        },
        "CompensateReduceStorage": {
            "Type": "ServiceTask",
            "ServiceName": "storageService",
            "ServiceMethod": "compensateDecrease",
            "Input": [
                "$.[businessKey]",
                "$.[productId]",
                "$.[count]"
            ]
        },
        "CompensationTrigger": {
            "Type": "CompensationTrigger",
            "Next": "Fail"
        },
        "Succeed": {
            "Type":"Succeed"
        },
        "Fail": {
            "Type":"Fail",
            "ErrorCode": "PURCHASE_FAILED",
            "Message": "purchase failed"
        }
    }
}

上面 Json 文件中定义的 buyGoodsOnline,是状态机加载的入口,状态机会找到这个 name,然后把状态加载到自己的内存中。下面,我们再来总结一下电商案例中分布式事务状态流转过程:

4 状态机应用

上面的电商例子中,三个分支服务分别定义了三个 State,对应的 ServiceMethod 如下:

  • SaveOrder#saveOrder:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public boolean saveOrder(String businessKey, Order order) {
 logger.info("保存订单, businessKey:{}, order: {}", businessKey, order);
 orderDao.create(order);
 return true;
}
  • ReduceAccount#decrease
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public boolean decrease(String businessKey, Long userId, BigDecimal money) {
 return accountApi.decrease(businessKey, userId, money);
}
  • ReduceStorage#decrease
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public boolean decrease(String businessKey, Long productId, Integer count) {
 return storageApi.decrease(businessKey, productId, count);
}

状态机在启动的时候,需要把上面方法中的参数都传入,实例代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
StateMachineEngine stateMachineEngine = (StateMachineEngine) ApplicationContextUtils.getApplicationContext().getBean("stateMachineEngine");
Map<String, Object> startParams = new HashMap<>(3);
String businessKey = String.valueOf(System.currentTimeMillis());
startParams.put("businessKey", businessKey);
startParams.put("order", order);
startParams.put("mockReduceAccountFail", "true");
startParams.put("userId", order.getUserId());
startParams.put("money", order.getPayAmount());
startParams.put("productId", order.getProductId());
startParams.put("count", order.getCount());
//这里采用同步方法
StateMachineInstance inst = stateMachineEngine.startWithBusinessKey("buyGoodsOnline", null, businessKey, startParams);

5 状态机原理

下面这张图来自于 Seata 官网,主要讲解了状态机的工作原理:

  1. 状态机启动时,首先启动了全局事务;
  2. 将状态机的参数记录在本地 seata_state_machine_inst 表;
  3. 向 Seata Server 注册分支事务;
  4. 执行 StateA 并记录状态到本地数据库,同时会产生路由事件放入 EventQueue,执行 StateB 时取出路由消息触发执行。同样 StateB 执行时也会产生路由消息放入 EventQueue;
  5. 从 EventQueue 取出路由消息执行 StateC;
  6. 状态机结束流程,提交或回滚全局事务。

6 高可用

Seata 中的状态机并不是独立部署,而是内嵌在应用中,由于状态机上下文和执行日志都记录在本地数据库中,所以状态机本身是无状态的。

状态机启动时,会发送状态到 Seata Server,当一个应用宕机后,Seata Server 能感知到,并会把恢复请求发送到存活的实例,收到请求的实例从数据库取出状态机上下文和执行日志进行恢复。如下图:

7 总结

本文讲解了分布式事务中间件 Seata 给 Saga 模式设计的状态机使用方式和原理。状态机在我们的日常工作中使用非常广泛,希望 Seata 的设计能对我们设计状态机提供思路和参考。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-03-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 君哥聊技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
聊聊seata中saga模式的实现
saga模式是分布式事务中使用比较多的一种模式,主要应用在多节点长流程的应用中,对一个全局事务,如果某个节点抛出了异常,则从当前这个节点依次往前补偿事务。一阶段正向服务和二阶段补偿服务都需要由业务代码来实现。今天我们就来看看它的源码实现。
jinjunzhu
2021/01/05
3.7K0
聊聊seata中saga模式的实现
Seata-Saga模式 原理
状态机设计组件:seata-saga-statemachine-designer 状态机在线画图工具:saga_designer
全栈程序员站长
2022/11/04
6400
Seata-Saga模式 原理
seata saga模式_lua状态机
目前业界公认 Saga 是作为长事务的解决方案。而seata作为目前最流行的分布式事物解决方案也提供了Saga的支持。而采用Seata的Saga模式进行事物控制,核心就是通过状态机来进行控制,本文重点介绍Seata Saga状态机设计器的使用实战。
全栈程序员站长
2022/09/30
1K1
seata saga模式_lua状态机
分布式事务XA、AT、TCC、SAGA
假设系统中有3个服务,分别是订单服务、账户服务、库存服务,用户在下一个订单之后会扣除用户的余额,同时扣减库存容量。在这样的场景下扣款和扣库存需要强一致性保证。就可能会使用到分布式事务解决方案。
benym
2022/07/14
3.5K0
分布式事务XA、AT、TCC、SAGA
学习分布式事务Seata看这一篇就够了,建议收藏
学习Seata分布式事务看这一篇就够了 一、事务的特性 二、本地事务与分布式事务 三、分布式事务理论依据 3.1、CAP定律 3.2、BASE理论 四、Seata简介 4.1、Seata是什么 4.2、官网地址 4.3、Seata基本架构 4.4、分布式事务解决方案 4.4.1、Seata-AT模式 4.4.2、Seata-XA模式 4.4.2.1、XA模式 什么是XA协议 Seata的事务模式 4.4.2.2、Seata的XA模式 为什么要在Seata中支持XA XA的价值 4.4.2.3、项目中应用XA模式 4.4.2.4、XA模式如何切换 4.4.3、Seata-TCC事务模式 4.4.3.1、什么是TCC 4.4.3.2、Seata的TCC模式 4.4.4、Seata-Saga事务模式 4.4.4.1、基本概念 4.4.4.2、为什么需要Saga 4.4.4.3、Saga状态机 4.4.4.4、Saga状态机设计器 4.5、四种模式的对比 五、部署Seata TC服务 5.1、下载seata-server 5.2、解压修改配置 5.3、初始化数据库配置 5.4、Nacos配置中心添加配置 5.5、测试启动TC服务 六、项目集成Seata 6.1、业务背景 6.2、数据表创建 6.3、搭建基本服务 6.3.1、代码基本结构 6.3.2、pom.xml引入依赖 6.3.3、配置文件application.yml 6.3.4、创建订单接口 6.3.5、声明Feign接口 6.3.6、测试验证 6.4、使用Seata全局事务注解@GlobalTransactional 6.5、配置数据源代理 6.6、启动服务测试
小小Java开发者
2023/11/25
7.9K0
学习分布式事务Seata看这一篇就够了,建议收藏
对比 5 种分布式事务方案,还是宠幸了阿里的 Seata(原理 + 实战)
好长时间没发文了,最近着实是有点忙,当爹的第 43 天,身心疲惫。这又赶上年底,公司冲 KPI 强制技术部加班到十点,晚上孩子隔两三个小时一醒,基本没睡囫囵觉的机会,天天处于迷糊的状态,孩子还时不时起一些奇奇怪怪的疹子,总让人担惊受怕的。
程序员小富
2020/12/14
9980
MassTransit | 基于StateMachine实现Saga编排式分布式事务
状态机作为一种程序开发范例,在实际的应用开发中有很多的应用场景,其中.NET 中的async/await 的核心底层实现就是基于状态机机制。状态机分为两种:有限状态机和无限状态机,本文介绍的就是有限状态机,有限状态机在任何时候都可以准确地处于有限状态中的一种,其可以根据一些输入从一个状态转换到另一个状态。一个有限状态机是由其状态列表、初始状态和触发每个转换的输入来定义的。如下图展示的就是一个闸机的状态机示意图:
圣杰
2023/01/31
1.2K1
分布式事务saga_分布式事务代码例子
  在前面文章《分布式事务》中介绍了几种分布式事务,其中Saga介绍了相关的概念,接下来介绍Saga使用案例,案例来源《微服务架构设计模式》。
全栈程序员站长
2022/09/29
1.1K0
分布式事务saga_分布式事务代码例子
图解7种分布式事务模型(一文带你掌握分布式事务)
Hello 我是方才,10人研发leader、4年团队管理&架构经验。文末,方才送你一份25年最新的架构师备考资料,记得领取哟!
方才编程_公众号同名
2025/04/02
1810
图解7种分布式事务模型(一文带你掌握分布式事务)
saga分布式事务_分布式事务原理
saga是分布式事务领域里一个非常重要的事务模式,特别适合解决出行订票这类的长事务,本文将深度剖析saga事务的设计原理,以及在解决订票问题上的最佳实践
全栈程序员站长
2022/09/29
1.6K0
saga分布式事务_分布式事务原理
微服务(十六)——Seata 分布式事务框架
单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三三 个服务来完成。此时每个服务内部的数据一致性由本地事务来保证, 但是全局的数据一致性问题没法保证。
不愿意做鱼的小鲸鱼
2022/10/05
1.9K0
微服务(十六)——Seata 分布式事务框架
搞懂Seata分布式事务AT、TCC、SAGA、XA模式选型
🍁 作者:知识浅谈,CSDN签约讲师,CSDN原力作者,后端领域优质创作者,热爱分享创作 💒 公众号:知识浅谈 📌 擅长领域:全栈工程师、爬虫、ACM算法 Seata分布式事务AT、TCC、SAGA、XA模式选型总结 🤞这次都给他拿下🤞 正菜来了⛳⛳⛳ 分布式事务 Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata将为用户提供了AT、TCC、SAGA和XA事务模式,为用户打造一站式的分布式解决方案。 🎈AT模式 🍮实现原理 阿里SE
知识浅谈
2022/11/13
2.7K0
搞懂Seata分布式事务AT、TCC、SAGA、XA模式选型
Spring Cloud 中的分布式事务,附源码《一》
在开发我的开源项目 prex 时,加入工作流,解决工作流用户与当前系统用户同步问题时,涉及到远程调用操作两个数据库所产生的事务问题,比如系统用户在增加用户同步工作流用户时,系统用户添加成功,工作流用户没有添加成功,则造成数据不一致问题,本地事务无法回滚,那么则使用分布式事务解决方案。
搜云库技术团队
2019/12/02
1.1K0
分布式柔性事务之Saga详解
Saga模型起源于1987年 Hector Garcia-Molina,Kenneth Salem 发表的论文《Sagas》,是分布式事务相关概念最早出现的。
玄姐谈AGI
2020/06/28
1.7K0
Java学习笔记-微服务(9)-终-分布式事务Seata
在微服务中,一定存在多个数据库,那么多个数据库间如何处理分布式事务?对于分布式事务问题,有哪些解决方案?
咸鱼程序员
2025/03/09
1540
Java学习笔记-微服务(9)-终-分布式事务Seata
分布式事务 | 使用DTM 的Saga 模式
前面章节提及的MassTransit、dotnetcore/CAP都提供了分布式事务的处理能力,但也仅局限于Saga和本地消息表模式的实现。那有没有一个独立的分布式事务解决方案,涵盖多种分布式事务处理模式,如Saga、TCC、XA模式等。有,目前业界主要有两种开源方案,其一是阿里开源的Seata,另一个就是DTM。其中Seata仅支持Java、Go和Python语言,因此不在.NET 的选择范围。DTM则通过提供简单易用的HTTP和gRPC接口,屏蔽了语言的无关性,因此支持任何开发语言接入,目前提供了Go、Python、NodeJs、Ruby、Java和C#等语言的SDK。 DTM,全称Distributed Transaction Manager,是一个分布式事务管理器,解决跨数据库、跨服务、跨语言更新数据的一致性问题。它提供了Saga、TCC、 XA和二阶段消息模式以满足不同应用场景的需求,同时其首创的子事务屏障技术可以有效解决幂等、悬挂和空补偿等异常问题。
圣杰
2023/02/10
1.8K0
分布式事务 | 使用DTM 的Saga 模式
聊聊分布式解决方案Saga模式
Saga模式使用一系列本地事务来提供事务管理,而一个本地事务对应一个Saga参与者,在Saga流程里面每一个本地事务只操作本地数据库,然后通过消息或事件来触发下一个本地事务,如果其中一个本地事务失败了,Saga就会执行一系列补偿事务来实现回滚操作。(补偿事务简单来讲就是对之前本地事务做的修改导致不一致的情况执行反向操作来消除掉不一致的状态)。
Ryan_OVO
2023/10/19
3900
聊聊分布式解决方案Saga模式
基于 Seata Saga 设计更有弹性的金融应用
Seata 意为:Simple Extensible Autonomous Transaction Architecture,是一套一站式分布式事务解决方案,提供了 AT、TCC、Saga 和 XA 事务模式,本文详解其中的 Saga 模式。
用户5397975
2019/11/28
1.5K0
基于 Seata Saga 设计更有弹性的金融应用
再谈分布式事务
三年前,我写了第一篇和分布式事务相关的文章再有人问你分布式事务,把这篇扔给他,后面陆续也写了一些和分布式事务相关的文章:
用户5397975
2022/02/24
4210
再谈分布式事务
分布式事务之TCC与SAGA
在《关于分布式事务的理解》,介绍了可靠消息队列的实现原理,虽然它也能保证最终的结果是相对可靠的,过程也足够简单(相对于 TCC 来说),但现在你已经知道,可靠消息队列的整个实现过程完全没有任何隔离性可言。虽然在有些业务中,有没有隔离性不是很重要,比如说搜索系统。但在有些业务中,一旦缺乏了隔离性,就会带来许多麻烦。Fenix's Bookstore 在线书店的场景事例中,如果缺乏了隔离性,就会带来一个显而易见的问题:超售。
燃192
2023/04/10
7450
分布式事务之TCC与SAGA
相关推荐
聊聊seata中saga模式的实现
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验