【分布式事务】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_storage
        SET used = used + #{num}
        WHERE
            product_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");
    }
    @Override
    public 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.mfbz.cn/a/740105.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

c++分隔字符串

可以使用getline函数。 有两个版本&#xff1a; 至于为什么可以使用getline函数返回值作为while的判断条件&#xff0c;cprimer中表述如下&#xff1a;

[MySql]两阶段提交

文章目录 什么是binlog使用binlog进行恢复的流程 什么是redolog缓冲池redologredolog结构 两阶段提交 什么是binlog binlog是二进制格式的文件&#xff0c;用于记录用户对数据库的修改&#xff0c;可以作用于主从复制过程中数据同步以及基于时间点的恢复&#xff08;PITR&…

Docker(六)-本地镜像发布到私有库

1.下载镜像Docker Registry 用于搭建私人版本Docker Hub docker pull registry2.运行私有库Registry 运行私有库Registry&#xff0c;相当于本地有个私有Docker hubdocker run -d -p hostPort:containerPort -v 【宿主机目录】:【容器目录】 --privilegedtrue 【私有库镜像】…

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

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

若依-前后端分离项目学习

第一天&#xff08;6.24&#xff09; 具体参考视频 b站 楠哥教你学Java 【【开源项目学习】若依前后端分离版&#xff0c;通俗易懂&#xff0c;快速上手】 https://www.bilibili.com/video/BV1HT4y1d7oA/?share_sourcecopy_web&vd_sourcecd9334b72b49da3614a4257…

“湖北建筑安全员C证考试合格证书不能打印?可能是你犯了这个错误!“

"湖北建筑安全员C证考试合格证书不能打印&#xff1f;可能是你犯了这个错误&#xff01;" 湖北建筑安全员C证考试合格证书不能打印咋回事 目前建筑安全员C证都是全国联网的&#xff0c;在湖北考的建筑安全员C证在外省也可以正常使用or调转。全国工程质量安全监管信息…

【扫雷游戏】C语言详解

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属专栏&#xff1a;C语言 &#x1f680;本系列文章为个人学习…

从基础到高级:视频直播美颜SDK的开发教学

本篇文章&#xff0c;小编将从基础到高级&#xff0c;详细讲解视频直播美颜SDK的开发过程&#xff0c;帮助开发者更好地掌握这一技术。 一、基础知识 什么是视频直播美颜SDK&#xff1f; 视频直播美颜SDK包含了一系列用于视频处理的功能模块&#xff0c;特别是美颜效果的实现…

封装vuetify3中v-time-picker组件,并解决使用时分秒类型只能在修改秒之后v-model才会同步更新的问题

目前时间组件还属于实验室组件&#xff0c;要使用需要单独引入&#xff0c;具体使用方式查看官网 创建公共时间选择器组件 common-time-pickers.vue 子组件页面 <template><div><v-dialog v-model"props.timeItem.isShow" activator"parent&q…

对于GPT-5的些许期待

目录 1.概述 2.GPT-5技术突破预测 3.智能系统人类协作 3.1. 辅助决策 3.2. 增强创造力 3.3. 处理复杂任务 3.4.人机协同的未来图景 4.迎接AI技术变革策略 4.1.教育方面 4.2.职业发展方面 4.3.政策制定方面 4.4.人才与技能培养 1.概述 GPT-5作为下一代大语言模型&a…

ONLYOFFICE 8.1全新升级,智能办公体验再升级,引领未来工作新潮流!

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀ONLYOFFICE 8.1 &#x1f4d2;1. ONLYOFFICE简介&#x1f4d9;2. ONLYOFFICE特点&#x1f4d5;3. ONLYOFFICE功能⛰️PDF 文件编辑器&#x1…

【Android】实现图片和视频混合轮播(无限循环、视频自动播放)

目录 前言一、实现效果二、具体实现1. 导入依赖2. 布局3. Banner基础配置4. Banner无限循环机制5. 轮播适配器6. 视频播放处理7. 完整源码 总结 前言 我们日常的需求基本上都是图片的轮播&#xff0c;而在一些特殊需求&#xff0c;例如用于展览的的数据大屏&#xff0c;又想展…

三维点云目标识别对抗攻击研究综述

源自&#xff1a;电子与信息学报 作者&#xff1a;刘伟权 郑世均 郭宇 王程 注&#xff1a;若出现无法显示完全的情况&#xff0c;可 V 搜索“人工智能技术与咨询”查看完整文章 摘 要 当前&#xff0c;人工智能系统在诸多领域都取得了巨大的成功&#xff0c;其中深度学…

云原生架构:未来应用程序设计和部署的革新

目录 前言1. 云原生架构的概述1.1 什么是云原生架构1.2 云原生架构的核心理念 2. 云原生架构的核心特征2.1 容器化应用2.2 微服务架构2.3 自动化管理 3. 云原生架构的优势3.1 弹性和可伸缩性3.2 高可用性和容错性3.3 快速交付和持续部署 4. 实施云原生架构的关键技术4.1 容器编…

对比A100和4090:两者的区别以及适用点

自2022年年末英伟达发布4090芯片以来&#xff0c;这款产品凭借着其优异的性能迅速在科技界占据了一席之地。现如今&#xff0c;不论是在游戏体验、内容创作能力方面还是模型精度提升方面&#xff0c;4090都是一个绕不过去的名字。而A100作为早些发布的产品&#xff0c;其优异的…

【高性能计算笔记】

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

宝宝早教电子图书 酷得电子方案

宝宝早教发声书是一种专为婴幼儿设计的图书&#xff0c;旨在通过有趣的图画和声音来吸引宝宝的注意力&#xff0c;帮助他们学习语言、认知和发展各种技能。这类书籍通常包括以下特点&#xff1a; 鲜艳的图画&#xff1a;发声书通常配有色彩鲜艳、形象生动的图画&#xff0c;以…

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

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

【44 Pandas+Pyecharts | 全国海底捞门店数据分析可视化】

文章目录 &#x1f3f3;️‍&#x1f308; 1. 导入模块&#x1f3f3;️‍&#x1f308; 2. Pandas数据处理2.1 读取数据2.2 查看数据信息2.3 查看描述信息 &#x1f3f3;️‍&#x1f308; 3. Pyecharts数据可视化3.1 各省海底捞门店数量分布柱状图3.2 各省海底捞门店数量分布地…

西南地区某大型钢厂蓝鹏测控又一组测径仪设备投入交付使用

近日&#xff0c;蓝鹏测控为西南地区某大型钢铁厂定制生产的又一台测径仪完成交付安装, 并通过了现场调试验收。这些智能测径仪被广泛应用于各种轧钢生产线&#xff0c;用于检测不同规格的圆棒圆管钢材等。这些设备能够精确测量棒材管材的外径、椭圆度、可以实时显示最大直径, …