【分布式事务】Seata AT实战

目录

Seata 介绍

Seata 术语

Seata AT 模式

介绍

实战(nacos注册中心,db存储)

部署 Seata

实现 RM

实现 TM

可能遇到的问题

1. Seata 部署成功,服务启动成功,全局事务不生效 

2. 服务启动报错 can not get cluster name in registry config ‘service.vgroupMapping.xx‘, please make sure registry

debug 调试

undo_log

branch_table

global_table

lock_table

原理

RM & TM 如何与 TC 建立连接

一阶段步骤

二阶段步骤

AT 模式如何避免脏写和脏读

总结


项目地址:GitHub - chenyukang1/mall

本文实战基于如下版本:
JDK 8
Spring Boot  2.6.11
Spring Cloud 2021.0.4
Spring Cloud Alibaba 2021.0.4.0
Seata 1.5.2

Seata 介绍

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案

  • 对业务无侵入:即减少技术架构上的微服务化所带来的分布式事务问题对业务的侵入

  • 高性能:减少分布式事务解决方案所带来的性能消耗

源码:https://github.com/seata/seata(opens new window)

文档:Apache Seata

Seata 术语

TC (Transaction Coordinator) 事务协调者 :维护全局和分支事务的状态,驱动全局事务提交或回滚

TM (Transaction Manager) 事务管理器:开始全局事务、提交或回滚全局事务

RM (Resource Manager) 资源管理器:管理分支事务处理的资源,与 TC 交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚

image-20210215174627345

Seata AT 模式

介绍

AT 模式是 Seata 创新的一种非侵入式的分布式事务解决方案,Seata 在内部做了对数据库操作的代理层,我们使用 Seata AT 模式时,实际上用的是 Seata 自带的数据源代理 DataSourceProxy,Seata 在这层代理中加入了很多逻辑,比如插入回滚 undo_log 日志,检查全局锁等

在 AT 模式下,用户只需关注自己的业务SQL,用户的业务SQL 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作

实战(nacos注册中心,db存储)

部署 Seata

完成 db 建表,nacos 发布 seataServer.properties 配置,最后启动 seata,参考:Docker compose部署 | Apache Seata

实现 RM

1. 创建订单和库存服务的 DB 和表

-- 库存服务DB执行
CREATE TABLE `tab_storage` (`id` bigint(11) NOT NULL AUTO_INCREMENT,`product_id` bigint(11) DEFAULT NULL COMMENT '产品id',`total` int(11) DEFAULT NULL COMMENT '总库存',`used` int(11) DEFAULT NULL COMMENT '已用库存',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO `tab_storage` (`product_id`, `total`,`used`)VALUES ('1', '96', '4');
INSERT INTO `tab_storage` (`product_id`, `total`,`used`)VALUES ('2', '100','0');
-- 订单服务DB执行
CREATE TABLE `tab_order` (`id` bigint(11) NOT NULL AUTO_INCREMENT,`user_id` bigint(11) DEFAULT NULL COMMENT '用户id',`product_id` bigint(11) DEFAULT NULL COMMENT '产品id',`count` int(11) DEFAULT NULL COMMENT '数量',`money` decimal(11,0) DEFAULT NULL COMMENT '金额',`status` int(1) DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完成',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

2. 各数据库加入undo_log

CREATE TABLE `undo_log` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`branch_id` bigint(20) NOT NULL,`xid` varchar(100) NOT NULL,`context` varchar(128) NOT NULL,`rollback_info` longblob NOT NULL,`log_status` int(11) NOT NULL,`log_created` datetime NOT NULL,`log_modified` datetime NOT NULL,`ext` varchar(100) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

3. 编写业务代码

实现创建订单 & 锁库存逻辑 

    public boolean save(long userId, long productId) {OrderEntity orderEntity = new OrderEntity();orderEntity.setUserId(userId);orderEntity.setProductId(productId);orderEntity.setCount(1);orderEntity.setMoney(BigDecimal.valueOf(80));orderEntity.setStatus(0);return save(orderEntity);}
    public boolean lockStock(long productId, long used) {return storageDao.lockStock(productId, used) > 0;}
    <update id="lockStock">UPDATE tab_storageSET used = used + #{num}WHEREproduct_id = #{productId}AND total - used >= #{num}</update>
实现 TM

TM 作为事务全局管理者,也是全局事务的发起者,通过远程调用订单和库存服务,开启全局事务

1. 编写 Feign 远程调用

2. 开启全局事务

    @GlobalTransactional(timeoutMills = 300000)@GetMapping("/submitOrder")public R submitOrder(long userId, long productId, long used) {businessService.submitOrder(userId, productId, used);return R.ok().put("res", "success");}
    @Overridepublic void submitOrder(long userId, long productId, long used) {log.info("submitOrder begin ... xid: {}", RootContext.getXID());R lockStock = storageFeignService.lockStock(productId, used);boolean lockRes = (boolean) lockStock.get("res");if (!lockRes) {throw new RuntimeException("lock stock fail");}R save = orderFeignService.save(userId, productId);boolean saveRes = (boolean) save.get("res");if (!saveRes) {throw new RuntimeException("save order fail");}}
可能遇到的问题
1. Seata 部署成功,服务启动成功,全局事务不生效 

首先检查是否有全局事务 ID xid,请求的时候会通过请求头传递到下游服务,没有这个一切白搭。可以直接在全局事务的入口打印出来看看,代码示例:

log.info("submitOrder begin ... xid: {}", RootContext.getXID()); 

如果全局事务 ID 为 null,可能的原因有:

  • 版本问题:如果选用较低版本的 Seata(比如v1.5.2),适当降低 Spring Boot、Spring Cloud、Spring Cloud Alibaba 的配套版本,实在不确定可以参考文章开头我的版本配置
2. 服务启动报错 can not get cluster name in registry config ‘service.vgroupMapping.xx‘, please make sure registry

这个报错是因为 TM/RM 的 service.vgroupMapping.xx 配置与 Seata Server 的不一致,可以按如下方式排查:

    1. TM/RM 配置指定了事务群组

     

    2. 服务端有对应的配置(以 nacos 为例)

    

    3. TM/RM 的 nacos 注册中心必须和 Seata 在同一 namespace、同一 group(默认是 SEATA_GROUP) 下
    4. Seata 使用 nacos 部署,它读的配置默认是 seataServer.properties,而 TM/RM 的配置要通过官方提供的脚本发布到 nacos 与 Seata 同一命名空间下 ,推荐阅读:Nacos 配置中心 | Apache Seata

debug 调试

我们在全局事务开启后,结束前打断点,看看数据库发生了什么

undo_log

发现 RM 的 undo_log 表中都生成了一条记录,以库存表为例,字段的数据如下

{"@class":"io.seata.rm.datasource.undo.BranchUndoLog","xid":"116.198.200.0:8091:45570721124696075","branchId":45570721124696077,"sqlUndoLogs":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.undo.SQLUndoLog","sqlType":"UPDATE","tableName":"tab_storage","beforeImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords","tableName":"tab_storage","rows":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Row","fields":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"id","keyType":"PRIMARY_KEY","type":-5,"value":["java.lang.Long",2]},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"used","keyType":"NULL","type":4,"value":44}]]}]]},"afterImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords","tableName":"tab_storage","rows":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Row","fields":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"id","keyType":"PRIMARY_KEY","type":-5,"value":["java.lang.Long",2]},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"used","keyType":"NULL","type":4,"value":46}]]}]]}}]]}
  • xid:全局事务 id
  • branchId:分支事务 id
  • beforeImage:事务前快照
  • afterImage:事务后快照
branch_table

TC 的 branch_table 新增两条记录,表示开启两个分支事务

global_table

global_table 新增一条记录,表示开启一个全局事务

lock_table

lock_table 新增两条记录,表示两个 RM 一阶段开启了事务,但事务未提交,都持有行锁

原理

RM & TM 如何与 TC 建立连接

在启动阶段,RM/TM 会在控制台打出注册信息,即与 TC 建立了连接

NettyPool create channel to transactionRole:TMROLE,address:116.198.200.0:8091,msg:< RegisterTMRequest{applicationId='seata-server', transactionServiceGroup='business-tx-service-group'} >

不难看出,它们之间的通信基于 Netty,Netty 作为一款高性能的 RPC 通信框架,保证了 TC 与 RM 之间的高效通信

但它又是怎么区分 RM 还是 TM 的?毕竟配置文件都一样。答案是 @GlobalTransactional 注解,这个注解表示开启全局事务,Seata 认为标注这个注解的客户端就是 TM,这类注解都是基于 Spring AOP 机制,对使用了注解的 Bean 方法分配对应的拦截器进行增强,来完成对应的处理逻辑。而 GlobalTransactionScanner 这个 Spring Bean,就承载着为各个注解分配对应的拦截器的职责

推荐阅读:Seata应用侧启动过程剖析——RM & TM如何与TC建立连接 | Apache Seata

一阶段步骤
  1. RM 写表的过程,Seata 会拦截业务 SQL,首先解析 SQL 语义
  2. 在业务数据被更新前,做一次快照,生成 beforeImage
  3. 执行业务 SQL
  4. 在业务数据更新之后,做一次快照,生成 afterImage,最后生成行锁

以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性

二阶段步骤

因为业务 SQL 在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可

  • 正常:TM 执行成功,通知 TC 全局提交,TC 此时通知所有的 RM 提交成功,删除 undo_log  回滚日志
  • 异常:TM 执行失败,通知 TC 全局回滚,TC 此时通知所有的 RM 进行回滚,根据 undo_log 反向操作,使用 beforeImage 还原业务数据,删除 undo_log。但在还原前要首先要校验脏写,对比 “数据库当前业务数据” 和 “afterImage”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写会按配置的策略处理
AT 模式如何避免脏写和脏读

推荐阅读:Seata AT 模式 | Apache Seata

总结

优点:

  • 无侵入性:本质上是通过代理数据源实现 2PC 模式,对业务无侵入性,开发成本低

缺点:

  • 不适合高并发场景:AT 模式的实现依赖数据库锁机制,本地事务依赖行锁来实现读写隔离,以电商中常见的提交订单业务为例,提交订单的业务流程涉及到创建订单,锁库存等等,订单是用户维度的数据,并发度不高;但库存记录是 sku 级别的,加行锁很容易让后续的读写请求都阻塞

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[java]集合类stream的相关操作

1.对list中的map进行分组 下面例子中&#xff0c;根据高度height属性进行分组 List<Map<String, Float>>originalList new ArrayList<>();originalList.add(new HashMap<String,Float>() {{put("lng", 180.0f);put("lat",90f);…

C++使用Poco库封装一个FTP客户端类

0x00 Poco库中 Poco::Net::FTPClientSession Poco库中FTP客户端类是 Poco::Net::FTPClientSession , 该类的接口比较简单。 上传文件接口&#xff1a; beginUpload() , endUpload() 下载文件接口&#xff1a; beginDownload() , endDownload() 0x01 FTPCli类说明 FTPCli类…

CSS规则——font-face

font-face 什么是font-face&#xff1f; 想要让网页文字千变万化&#xff0c;仅靠font-family还不够&#xff0c;还要借助font-face&#xff08;是一个 CSS 规则&#xff0c;它允许你在网页上使用自定义字体&#xff0c;而不仅仅是用户系统中预装的字体。这意味着你可以通过提…

jemeter基本使用

后端关验签&#xff0c;设置请求头编码和token 配置编码和token

Linux安装minio及mc客户端(包含ARM处理器架构)

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

练习实践:ubuntu18.04安装、配置Nginx+PHP环境,两种配置方式,多站点

参考来源&#xff1a; https://help.aliyun.com/document_detail/464753.html https://www.cnblogs.com/laosan007/p/12803287.html https://blog.csdn.net/qq_55364077/article/details/132207083 【安装同版本7.2的php】 需要知道对应php和nginx的安装版本 需要安装php-fpm…

<sa8650>QCX Usecase 使用详解—如何在 QCX 框架中添加新的自定义Usecase/Pipeline

<sa8650>QCX Usecase 使用详解—如何在 QCX 框架中添加新的自定义Usecase/Pipeline 一、前言二、为 Usecase/Pipeline 创建新文件夹三、Create Usecase XML四、为 camxAutoo_Test 管道创建拓扑五、添加Usecase/Pipeline编译六、 使用 Qcarcam_Test 应用程序运行Usecase一、前…

【总线】AXI4第五课时:信号描述

大家好,欢迎来到今天的总线学习时间!如果你对电子设计、特别是FPGA和SoC设计感兴趣&#xff0c;那你绝对不能错过我们今天的主角——AXI4总线。作为ARM公司AMBA总线家族中的佼佼者&#xff0c;AXI4以其高性能和高度可扩展性&#xff0c;成为了现代电子系统中不可或缺的通信桥梁…

【高性能计算笔记】

第1章 - 高性能计算介绍 1. 概念&#xff1a; 高性能计算(High performance computing&#xff0c;缩写HPC)&#xff1a; 指通常使用很多处理器&#xff08;作为单个机器的一部分&#xff09;或者某一集群中组织的几台计算机&#xff08;作为单个计算资源操作&#xff09;的…

零门槛用AI,302.AI让人工智能变得简单易用

当下人工智能火爆&#xff0c;提到AI&#xff0c;几乎每个人都能说上几句&#xff0c;但是你真的会使用AI吗&#xff1f; 当涉及到如何实际使用AI时&#xff0c;许多人可能会觉得它太过高深莫测&#xff0c;从而产生一种距离感&#xff0c;不知如何开始。我和大家也一样&#x…

Android性能优化-内存优化

&#xff11;、为什么进行内存优化&#xff08;如果不进行内存优化&#xff09; APP运营内存限制&#xff0c;OOM导致APP崩溃 APP性能&#xff0c;流畅性&#xff0c;响应速度和体验 2、Android内存管理方式: Android系统内存分配与回收方式 APP内存限制机制 切换应用时&…

AGV选型要点及步骤,保证企业选择的AGV小车更实用

AGV AGV小车作为智能化物流仓储不可或缺的工具&#xff0c;在制造业得到了广泛的应用&#xff0c;市场需求呈现出井喷式增长。但是AGV市场还存在着很多问题&#xff0c;制造企业在产品选型时往往缺乏正确的引导。 AGV智能仓储 毫无疑问,我们的自动化物流系统已离不开AGV小车了,…

链在一起Chained Together没中文 超好用的一键汉化工具推荐

《链在一起》一款新的可联机冒险跑酷类游戏&#xff0c;游戏里玩家将与你的同伴被链在一起&#xff0c;然后开始你的旅程&#xff0c;在地狱的深处&#xff0c;任务是通过尽可能高的攀登逃离地狱。 每一次跳跃都需要完美的协调才能攀上平台并逃离灼热&#xff0c;穿越众多世界&…

服务器win10server,python安装paddleocr的踩坑日记

最近由于需要图像文字识别的简单业务&#xff0c;研究了一下&#xff0c;一是用大厂的文字识别api&#xff0c;如百度腾讯等&#xff0c;但这种免费版只有有限的调用次数&#xff0c;如百度只有每月只有1000次调用额度&#xff0c;个人也够用&#xff0c;但由于业务量大&#x…

深入测评:ONLYOFFICE 8.1 桌面编辑器究竟有多强大?

ONLYOFFICE 8.1桌面编辑器 文章目录 ONLYOFFICE 8.1桌面编辑器一、ONLYOFFICE的简介二、ONLYOFFICE 8.1新功能和改进2.1 轻松编辑器 PDF 文件2.2 用幻灯片版式快速修改幻灯片2.3 无缝切换文档编辑、审阅和查看模式2.4 改进从右至左语言的支持 & 新的本地化选项2.5 隐藏“连…

【权威发布】2024年文化、设计与社会科学国际会议(ICCDSS 2024)

2024年文化、设计与社会科学国际会议 2024 International Conference on Culture, Design, and Social Sciences 会议简介 2024年文化、设计与社会科学国际会议旨在为全球范围内的专家学者提供一个交流文化、设计与社会科学研究成果的平台。会议将围绕文化、设计与社会科学的前…

超越AnimateAnyone, 华中科大中科大阿里提出Unimate,可以根据单张图片和姿势指导生成视频。

阿里新发布的UniAnimate&#xff0c;与 AnimateAnyone 非常相似&#xff0c;它可以根据单张图片和姿势指导生成视频。项目核心技术是统一视频扩散模型&#xff0c;通过将参考图像和估计视频内容嵌入到共享特征空间&#xff0c;实现外观和动作的同步。 相关链接 项目&#xff1…

Scala入门【安装与使用、变量与数据类型、运算符、函数、条件判断、循环、字符串、面向对象、数组】

视频地址:Scala大专/本科专用课程_哔哩哔哩_bilibili 目录 P01【01Scala安装与使用】16:15 P02【02变量与数据类型】17:14 P03【03运算符】12:41 P04【04函数】16:40 P05【05条件判断】10:56 P06【06循环】13:33 P07【07字符串】19:09 P08【08面向对象】17:27 P09【0…

DVWA-CSRF-samesite分析

拿DVWA的CSRF为例子 接DVWA的分析&#xff0c;发现其实Impossible的PHPSESSID是设置的samesite1. 参数的意思参考Set-Cookie SameSite:控制 cookie 是否随跨站请求一起发送&#xff0c;这样可以在一定程度上防范跨站请求伪造攻击&#xff08;CSRF&#xff09;。 下面用DVWA CS…

使用Python进行数据分析和自动化

组织严重依赖数据分析和自动化来提高运营效率。在本文中&#xff0c;我们将使用 Python&#xff08;一种用于通用编程的高级编程语言&#xff09;的示例来研究数据分析和自动化的基础知识。 什么是数据分析&#xff1f; 数据分析是指检查、清理、转换和建模数据的过程&#xf…