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

大家好,我是君哥。

状态机在我们的工作中应用非常广泛,今天聊一聊分布式事务中间件 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

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

"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 文件定义如下:

"ChoiceAccountState":{"Type": "Choice","Choices":[{"Expression":"[SaveOrderResult] == true","Next":"ReduceAccount"}],"Default":"Fail"
}

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

3.3 Fail

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

"CompensationTrigger": {"Type": "CompensationTrigger","Next": "Fail"
}

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

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

{"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:

public boolean saveOrder(String businessKey, Order order) {logger.info("保存订单, businessKey:{}, order: {}", businessKey, order);orderDao.create(order);return true;
}
  • ReduceAccount#decrease

public boolean decrease(String businessKey, Long userId, BigDecimal money) {return accountApi.decrease(businessKey, userId, money);
}
  • ReduceStorage#decrease

public boolean decrease(String businessKey, Long productId, Integer count) {return storageApi.decrease(businessKey, productId, count);
}

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

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 的设计能对我们设计状态机提供思路和参考。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2871447.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

30.HarmonyOS App(JAVA)鸿蒙系统app多线程任务分发器

HarmonyOS App(JAVA)多线程任务分发器 打印时间&#xff0c;记录到编辑框textfield信息显示 同步分发&#xff0c;异步分发&#xff0c;异步延迟分发&#xff0c;分组任务分发&#xff0c;屏蔽任务分发&#xff0c;多次任务分发 参考代码注释 场景介绍 如果应用的业务逻辑比…

Docker进阶教程 - 1 Dockerfile

更好的阅读体验&#xff1a;点这里 &#xff08; www.doubibiji.com &#xff09; 1 Dockerfile Dockerfile 是做什么的&#xff1f; 我们前面说到&#xff0c;制作镜像的方法主要有两种方式&#xff1a; 使用 docker commit 命令&#xff1b;使用 Dockerfile 文件。 但是…

C语言学习过程总结(16)——指针(4)

一、数组名的理解 我们直接使用%p打印出地址来看看&arr【0】 和 arr的不同&#xff1a; int main() {int arr[10] { 1,2,3,4,5,6,7,8,9,10 };printf("&arr[0] %p\n", &arr[0]);printf("arr %p\n", arr);} 、 很容易看出来两者的输出…

最强AI换脸工具Rope使用教程,Rope整合包下载【全网最全安装步骤】

Rope的汉化整合包&#xff08;包含模型&#xff09;以及下面教程所涉及到的所有安装包我都打包好了&#xff0c;需要的小伙伴可以关注文章底部公众号&#xff0c;回复关键词【rope】获取。 AI换脸软件简介必读 Rope 是一个免费开源的 AI 换脸软件&#xff0c;它具有图形化界面…

MySQL之旅

本文字数&#xff1a;11653&#xff1b;估计阅读时间&#xff1a;30 分钟 审校&#xff1a;庄晓东&#xff08;魏庄&#xff09; 本文在公众号【ClickHouseInc】首发 介绍 "简单是终级的精致。"- --列奥纳多达芬奇 虽然我们喜欢在 ClickHouse 为用户宣布新功能&#…

【代码】提取图像轮廓坐标并保存为YOLOv8所需的txt格式

该段代码的应用场景为对图像标注过后&#xff0c;想要对图像进行裁切&#xff0c;但是标签不能裁切&#xff0c;所以将原图像按照标签进行二值化后&#xff0c;将二值化后的图像进行裁切&#xff0c;然后使用opencv对裁切后的图像进行处理&#xff0c;识别出白色区域轮廓&#…

用c++实现计数排序、颜色排序问题

3.3.1 计数排序 【问题】 假设待排序记录均为整数且取自区间[0,k],计数排序(count sort)的基本思想是对每一个记录x&#xff0c;确定小于x的记录个数&#xff0c;然后直接将x放在应该的位置。例如&#xff0c;小于x的记录个数是10,则x就位于第11个位置。 【想法】 对于待排序序…

vulnhub-----SickOS靶机

文章目录 1.信息收集2.curl命令反弹shell提权利用POC 1.信息收集 ┌──(root㉿kali)-[~/kali/vulnhub/sockos] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:10:3c:9b, IPv4: 10.10.10.10 Starting arp-scan 1.9.8 with 256…

移动端研发技术的进化历程

移动端研发技术 移动端研发技术主要分为原生开发和跨平台开发。本章主要介绍一下移动开发技术的过去、当下和未来&#xff0c;一步一步介绍移动技术的进化历程。 原生开发 原生应用程序是指某一个移动平台&#xff08;比如iOS或Android&#xff09;所特有的应用&#xff0c;使…

【C/C++】C语言开发者必读:迈向C++的高效编程之旅

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方…

《1w实盘and大盘基金预测 day5》

从周预测到每天的预测都非常准。 主要的问题&#xff0c;操作股票情绪起伏太大&#xff0c;对一些个股把握不准&#xff08;医疗乱我心&#xff09;&#xff0c;整体情况还是非常好的。得分A 本周行情展望&#xff08;基本得到验证&#xff09;&#xff1a; 大盘应该还是震荡…

章节2:单词本该这样记

为什么我们记不住单词&#xff1f; 单词不是被胡编乱造出来的&#xff0c;单词是有规律的&#xff0c;单词是符合人类的逻辑的。 单词实际意思结构意义历史文化 我们要怎么记单词&#xff1f; 掌握单词的结构规律了解与单词有关的历史文化灵活巧计&#xff0c;不要太拘泥于…

vue2+vant2+Laravel7 实现多图上传到七牛云

后端接口 1、路由&#xff0c;在 routes/api.php 中 Route::resource(photos, PhotoController)->only(store);2、创建对应控制器 <?php namespace App\Http\Controllers; use Illuminate\Http\Request;class PhotoController extends Controller {/**** 上传图片* p…

网络安全行业真的很内卷吗?

有一个特别流行的词语叫做“内卷”&#xff1a; 城市内卷太严重了&#xff0c;年轻人不好找工作&#xff1b;教育内卷&#xff1b;考研内卷&#xff1b;当然还有计算机行业内卷…… 这里的内卷当然不是这个词原本的意思&#xff0c;而是“过剩”“饱和”的替代词。 按照网络安…

【GPT-SOVITS-03】SOVITS 模块-生成模型解析

说明&#xff1a;该系列文章从本人知乎账号迁入&#xff0c;主要原因是知乎图片附件过于模糊。 知乎专栏地址&#xff1a; 语音生成专栏 系列文章地址&#xff1a; 【GPT-SOVITS-01】源码梳理 【GPT-SOVITS-02】GPT模块解析 【GPT-SOVITS-03】SOVITS 模块-生成模型解析 【G…

每日五道java面试题之mybatis篇(三)

目录&#xff1a; 第一题. MyBatis的框架架构设计是怎么样的?第二题. 为什么需要预编译?第三题. Mybatis都有哪些Executor执行器&#xff1f;它们之间的区别是什么&#xff1f;第四题. Mybatis中如何指定使用哪一种Executor执行器&#xff1f;第五题. Mybatis是否支持延迟加载…

龙芯新世界系统(安同AOCS OS)安装Cinnamon桌面最新版6.0.4

龙芯的新世界系统安同AOCS OS是十分优秀的操作系统&#xff0c;处于纯社区方式运行&#xff0c;她的各组件更新得很及时&#xff0c;很多组件都处于最新的状态&#xff0c;给我们安装使用最新的开源软件提供了很好的基础。由于本人一直使用Cinnamon桌面环境&#xff0c;各方面都…

鸿蒙开发实战:【Faultloggerd部件】

theme: z-blue 简介 Faultloggerd部件是OpenHarmony中C/C运行时崩溃临时日志的生成及管理模块。面向基于 Rust 开发的部件&#xff0c;Faultloggerd 提供了Rust Panic故障日志生成能力。系统开发者可以在预设的路径下找到故障日志&#xff0c;定位相关问题。 架构 Native In…

【Linux】对进程PCB的理解查看进程信息的方法

一、学习准备&#xff1a;对操作系统工作模式的理解 首先我们要清楚的是&#xff0c;操作系统是一个进行软硬件资源管理的软件。操作系统对下要管理好底层硬件。每一个硬件的生产产商都会给他们的产品提供对应的驱动程序&#xff0c;驱动程序是特定于某一硬件或系统设备的软件组…

【CTF web1】

CTF web 一、CTF web -PHP弱类型1、是否相等&#xff1f;2、转换规则: 二、CTF web -md5绕过1、若类型比较绕过2、null绕过3、碰撞绕过 三、习题 一、CTF web -PHP弱类型 1、是否相等&#xff1f; &#xff1a;在进行比较的时候&#xff0c;会先判断两种字符串的类型是否相等&…