SpringCloud Alibaba - Seata 四种分布式事务解决方案(XA、AT)+ 实践部署(上)

news2024/11/13 9:56:01

目录

一、Seata 分布式事务解决方案

1.1、XA 模式

1.1.1、XA模式理论

第一阶段:

第二阶段:

1.1.2、Seata 框架中的 XA 模式

第一阶段:

第二阶段:

1.1.3、XA 模式的优缺点

1.2.4、实现Seata 的 XA 模式

a)修改 application 文件(每一个参与事务的微服务)

b)给发起全局事务中的入口方法添加 @GlobalTransactional 注解

c)重启服务并测试

1.2、AT 模式

1.2.1、AT 模式理论

第一阶段:

第二阶段:

问题:XA模式 和 AT模式 的区别?

1.2.2、AT 模式脏写问题

1.2.3、解决办法:写隔离

1.2.4、AT 模式的优缺点

1.2.5、实现 AT 模式

a)建表

b)修改 application.yml 文件

c)重启服务并测试


一、Seata 分布式事务解决方案


1.1、XA 模式

1.1.1、XA模式理论

XA 模式是 X/Open 组织定义的一个分布式事务处理的标准,可以认为是分布式事务领域最早的标准了,所以几乎所有主流的数据库(mysql、oracle......)都是实现了这种标准. 换言之,这些数据库内部已经能够基于 XA 模式实现分布式事务了.

这种模式把分布式定义成了以下两个阶段

第一阶段:

这一阶段也叫做准备阶段,事务协调者 会向 资源管理器 发起一个准备的请求,也就是告诉他:“你可以去执行业务 sql 了啊,但是执行完了以后不可以提交!”. 随后,资源管理器 就执行数据库业务,然后将执行结果告诉 事务协调者(执行成功了,就是就绪状态;失败了,就是 fail 状态),这样 事务协调者 就可以根据结果判断下一个阶段要干什么.

第二阶段:

事务协调者收到 RM 的反馈之后,就会进行判断.  如果都执行成功了,就通知这些 RM:“你们可以提交了啊”,然后 RM 收到请求,就把事务提交了,整个事务也就结束了;如果有任意一个服务执行失败了,那么事务协调者就会通知这些RM,让他们全部回滚.

可以看出 XA 模式是一种强一致性的事务,软可用性,因为事务没有提交的时候,拿不到数据库锁,是不能进行其他操作的.

1.1.2、Seata 框架中的 XA 模式

Seata 的 XA 模式和上述讲的 XA 模式大体相似,只是多了一个 TM 的概念. 如下图

第一阶段:

Seata 实际上就是多了一个 TM 的概念, 也就是事务管理者. TM 一上来就是要去注册全局事务,作为分布式事务的入口,接下来就会去调用各个分支事务.  每个分支事务中又有一个 RM,RM 就回去 TC 里面注册分支事务,然后执行业务 sql,但是执行完后不能提交,只是去报告一下事务的状态.  到此,第一阶段结束.

第二阶段:

TM 这里的入口方法执行完了以后,就要去告诉 TC 说:“我这边执行完了,接下来就看你那边的情况了~”. 随后 TC 就会去检查各个分支事务报告的状态,然后向 RM 发送一个信号——如果第一阶段都执行成功了,那么就提交,反之,则进行回滚.   RM 收到信号之后就可以进行 提交/回滚 事务了.

1.1.3、XA 模式的优缺点

优点:

1. 支持强一致:执行完业务 sql 以后,不进行提交事务,而是等到 TC 协调完后,在进行 提交/回滚. 

2. 实现起来简单:因为数据库本身就支持 XA 模式,Seata 只是在数据库的 XA 模式上做了一层简单的封装.  如果只看核心部分,就没什么差别了.

缺点:

1. 弱可用性,性能差:第一阶段不提交,等待第二阶段才提交,这个过程中会占用数据库锁(相当于对系统资源的一种浪费).  一旦分支事务特别多的情况下,业务耗时就会很久.

2. 依赖数据库底层实现:虽然用起来简单了,但是如果我用的不是 mysql 这种关系型数据库,而是 redis 这种非关系型数据库就不行了.

1.2.4、实现Seata 的 XA 模式

a)修改 application 文件(每一个参与事务的微服务)

开启 seata 的 XA 模式

seata:
  data-source-proxy-mode: XA # 开启数据源代理的XA模式

b)给发起全局事务中的入口方法添加 @GlobalTransactional 注解

当前案例的全局事务入口是 订单服务中的 创建订单的 create 方法(案例架构如下图).

这里只需要在 create 方法上添加 @GlobalTransactional 注解即可

    @Override
    @GlobalTransactional
    public Long create(Order order) {
        // 创建订单
        orderMapper.insert(order);
        try {
            // 扣用户余额
            accountClient.deduct(order.getUserId(), order.getMoney());
            // 扣库存
            storageClient.deduct(order.getCommodityCode(), order.getCount());
        } catch (FeignException e) {
            log.error("下单失败,原因:{}", e.contentUTF8(), e);
            throw new RuntimeException(e.contentUTF8(), e);
        }
        return order.getId();
    }

c)重启服务并测试

Ps:这里不要忘了先启动 Nacos 和 TC 服务.

以下分别为用户余额和库存数量.

1. 这里使用 Postman 发送以下请求(减少 9 个库存数量,用户余额减少 200)

http://localhost:8082/order?userId=user202103032042012&commodityCode=100202003032041&count=9&money=200

2. 随后可以看到订单微服务返回 500 错误.

在扣款微服务中,先进行了扣款业务,但是随后 rm 又执行了回滚

在库存微服务中,可以看到扣减失败.

最后观察数据库中数据,可以看到事务已经回滚如初.

1.2、AT 模式

1.2.1、AT 模式理论

AT 模模式也是分阶段性提交的事务模型. 他的出现正式为了解决 XA 模式中资源锁定周期过长的一个缺陷.

AT 模式也分为两个阶段

第一阶段:

TM 去开启全局事务,作为分布式事务的入口,接着又会调用每一个分支事务 ,然后每个分支事务里的 rm 都会去注册分支事务,并执行本地的业务 sql,然后直接提交(这里就和 XA 不一样了). 因此 AT 模式就不需要像 XA 那样锁定资源,性能上要优于 XA 模式.

这里直接提交,万一有人失败了呢,没办法回滚不就导致状态不一致了吗?

实际上 AT 模式在执行 sql 之前,rm 会给当前数据生成一个快照,这个快照的名字叫 “undo log”,这就像是 redis 中 RDB 持久化时也会生成快照,那么即使服务重启,也可以根据快照恢复数据.  那么这样就即使有分支事务执行 sql 的时候失败了,也可以根据快照恢复如初,大胆提交就对了.

第二阶段:

TM 看到业务结束了,就会去通知 TC,那么 TC 就会判断是提交还是回滚.  如果分支事务的状态都是成功的,那就可以把第一阶段准备的快照给删了(删快照这个动作是异步的,因为第一阶段都成功了,也提交了,后面的事情就可以用一个线程独立去做,提高了效率).  如果第一阶段有人失败了,就要基于 undo log 恢复数据,恢复以后这个 log 也就没用了,最后也会删除.

问题:XA模式 和 AT模式 的区别?

1. XA模式在第一阶段的时候不会进行事务的提交,而 AT 模式执行完业务 sql 之后会立即提交事务,不会锁定资源,因此性能会好一些.

2. XA 依赖于数据库的机制来做回滚,而 AT 模式因为已经提交了,就不能回滚,他是通过给自己生成快照的方式来实现数据的恢复.

3. XA 模式是强一致,而 AT 是最终一致,因为 AT 在业务 sql 执行之后直接提交了,有人失败,有人成功,那么这个时候状态肯定是不一致的,也就是一个软状态,只有在第二阶段,基于快照恢复了数据,才能到达一个最终一致的效果.

1.2.2、AT 模式脏写问题

AT 模式在第一阶段执行完业务 sql 以后会直接提交,那么资源锁定的周期就比较短,效率高,但也正因为他提前释放了锁,就导致在高并发的场景下,会出现安全问题~

例如我这里有一个用户余额表,里面记录了 money 字段,表示余额. 

  1. 现在有一个线程开启了事务1,首先他会去获取数据库锁,接着生成快照(money = 100),然后执行业务 sql(把 money 值改成 90),最后提交事务,锁也就释放了. 
  2. 此时,另一个线程开启了 事务2 ,拿到锁以后保存快照(money = 90),执行业务 sql(把 money 值改成 80),最后提交释放锁.

到了第二阶段,假如说 事务1 要进行回滚(通过快照恢复数据),但是 事务1 的快照数据 money = 100!这个时候就出问题了,恢复了以后相当于 事务2 这哥们啥也没做. 这就是所谓的脏写问题.

1.2.3、解决办法:写隔离

上述过程出现的问题,归根结底还是隔离性的原因,如果第一阶段和第二阶段整体都是一个锁定状态,别人根本无法插入进来,因此 AT 模式就引入了一个东西叫 “全局锁”.

Ps:全局锁由 TC 来控制,用来记录当前哪个事务在操作哪种表的哪一行数据. 这张表了主要有 事务id、表名字、pk主键(记录这张表的哪一行数据).

  1. 有了全局锁,那么在刚刚上述栗子的 事务1中,执行完业务 sql 就会去获取全局锁,就表明这张表的这行数据是我 事务1 才能拥有的,此时就可以大胆的提交事务. 
  2. 当 事务2 执行完业务 sql 之后,也尝试去获取全局锁,但是由于 事务1 已经获取了,因此 事务2 就只能阻塞等待 事务1 释放锁.  事务1 想要释放全局锁得等到第二阶段结束,但是现在 事务2 又持有 DB 锁,事务1 也拿不到 DB 锁,因此事务就互相等待,直到 事务2 等待超时,就会进行回滚并释放 DB 锁.

这样也就实现了 事务1 在执行第一阶段和第二阶段的时候,任何人都不能来执行,起到了很好的 隔离效果.

问题一:有人可能就会说,那这不跟 XA  模式一样了?也锁定资源,这样别人也无法访问,效率不久下降了么?

这里实际上是有一个 锁粒度 上的差别.  XA 模式中是数据库锁不释放,意味着任何人都不能访问你这数据库的数据,而 AT 中的全局锁只是不能任何人操作这个表中的某一行数据,也就是说,同样是 account 表,你可以修改这张表其他所有不是余额的字段.  因此效率上还是高很多的.

问题二:有人可能就会说,隔离不彻底啊,可能有一种极端情况,就是在修改 money 的过程中,有一个其他 非Seata 管理的事务 也来修改 money 字段,这时候人家又不用获取全局锁,也可能出现脏写的问题

确实有可能,但是可能性非常低,有以下两个原因:

  1. 全局事务大多数情况下都是成功的,比如我这边转账,大多数情况下我不能明知道钱不够还去转超额的钱吧,那么第二阶段几乎就不会回滚. 
  2. 分布式事务耗时本来就长,或者说并发本来就比较低,像这种见缝插针的事务就更少见了.

但即使概论低,Seata 也还是考虑这种情况的. Seata 在管理的事务的时候,是保存了两个快照的,第一个是更新前的快照(money = 100),另一个是更新后的快照(money = 90). 在 Seata 管理的 事务1 执行到第二阶段的时候,他就会去对比数据库中的字段值 和 after-image(更新后的快照)是否一致,如果不一致,就知道其中有人动了手脚,此时他可能就会发一个短信、邮件、电话告诉你,需要人工介入了.

1.2.4、AT 模式的优缺点

优点:

高性能:第一阶段完成后直接提交,释放资源比较早,数据库的锁定时间短.

写隔离:利用全局锁机制,不仅实现了隔离,性能也依旧维持.

使用简单,无代码侵入:Seata 框架自动完成回滚和提交.

缺点:

软状态:由于第一阶段执行业务 sql 可能有人成功有人失败,并且已经提交,这种情况下,并没有达成一致,只有第二阶段快照恢复才最终一致.

影响性能:虽然相比 XA模式,性能要好很多,但是框架的快照功能多多少少都会影响一点性能.

1.2.5、实现 AT 模式

a)建表

AT 模式下会引入全局锁,因此需要 lock_table 表来记录(在 TC 服务关联的数据库中创建).

DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table`  (
  `row_key` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `xid` varchar(96) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `transaction_id` bigint(20) NULL DEFAULT NULL,
  `branch_id` bigint(20) NOT NULL,
  `resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `table_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `pk` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `gmt_create` datetime NULL DEFAULT NULL,
  `gmt_modified` datetime NULL DEFAULT NULL,
  PRIMARY KEY (`row_key`) USING BTREE,
  INDEX `idx_branch_id`(`branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

AT 模式还需要保存快照,因此需要 undo_log 表来记录(在 微服务 关联的数据库中导入).

DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log`  (
  `branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
  `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'global transaction id',
  `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'undo_log context,such as serialization',
  `rollback_info` longblob NOT NULL COMMENT 'rollback info',
  `log_status` int(11) NOT NULL COMMENT '0:normal status,1:defense status',
  `log_created` datetime(6) NOT NULL COMMENT 'create datetime',
  `log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
  UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'AT transaction mode undo table' ROW_FORMAT = Compact;

b)修改 application.yml 文件

将事务模式修改为 AT 模式即可.

seata:
  data-source-proxy-mode: AT # 开启数据源代理的AT模式

c)重启服务并测试

 以下分别为用户余额和库存数量.

1. 这里使用 Postman 发送以下请求(减少 9 个库存数量,用户余额减少 200)

2. 随后可以看到订单微服务返回 500 错误.

3. 扣款服务中进行扣款业务,最后回滚

4. 检查数据库数据回滚成功~

 

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

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

相关文章

建筑施工行业招投标资源众包分包系统站点开发

一款针对建筑、施工行业开发的程序系统平台,运营方可以招募企业发布招投标信息以及招聘信息。 核心功能:一、项目招投标众包发布和投标 企业可以根据自身资源或者实际需求发布参与招投标信息,程序后台可以管理、审核用户发布的信息。参与招…

【C++】AVL树 红黑树

AVL树 AVL树也是二叉搜索树的一种。因为对于普通的二叉搜索树,当插入的数据在有序或接近有序的情况下,二叉搜索树很可能退化成单支树,导致查找效率低下。而AVL树就很好的解决了这个问题。 首先,AVL树是一棵二叉搜索树。同时对于A…

appscan的两种手动探索扫描方式

文章目录 一、使用火狐FoxyProxy浏览器代理探索二、使用appscan内置浏览器探索 一、使用火狐FoxyProxy浏览器代理探索 首先火狐浏览器需安装FoxyProxy 先在扩展和主题里搜FoxyProxy 选FoxyProxy Standard,然后添加到浏览器就行 添加后浏览器右上角会有这个插件 打开apps…

JVM篇---第三篇

系列文章目录 文章目录 系列文章目录一、什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?二、Java内存结构三、说说对象分配规则一、什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”? Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文…

Maven 中引用其他项目jar包出现BOOT-INF问题

问题 在B项目中引入A项目的类,但是发现怎么也引入不进来 A项目打包之后,想在B项目中引用jar 在B项目中发现类文件无法引用 参考网上进行清缓存等一系列操作都没有解决。 最后发现引用的jar包中包含BOOT-INF, 然后去A项目中查找&#xff…

云原生边缘计算KubeEdge安装配置

1. K8S集群部署,可以参考如下博客 请安装k8s集群,centos安装k8s集群 请安装k8s集群,ubuntu安装k8s集群 2.安装kubEedge 2.1 编辑kube-proxy使用ipvs代理 kubectl edit configmaps kube-proxy -n kube-system #修改kube-proxy#大约在40多行…

【动手学深度学习-Pytorch版】Transformer代码总结

本文是纯纯的撸代码讲解,没有任何Transformer的基础内容~ 是从0榨干Transformer代码系列,借用的是李沐老师上课时讲解的代码。 本文是根据每个模块的实现过程来进行讲解的。如果您想获取关于Transformer具体的实现细节(不含代码)可…

多个浮点数精确求和math.fsum()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 多个浮点数精确求和 math.fsum() 选择题 执行结果输出为1.0的函数是? import math a[0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1] print("【执行】print(sum(a))") print(sum(a)) p…

【应用层协议】HTTPS的加密流程

文章目录 1. 认识HTTPS2. 密文3. HTTPS加密流程3.1 对称加密3.2 非对称加密3.3 证书 1. 认识HTTPS HTTPS(超文本传输协议安全)也是一个应用层协议,它是在HTTP协议的基础上引入了一个加密层。 也就是HTTP协议传输文本的方式是明文,…

7-16 城市间紧急救援 (综合最短路练习)

本题链接:PTA | 程序设计类实验辅助教学平台 题目: 样例: 输入 4 5 0 3 20 30 40 10 0 1 1 1 3 2 0 3 3 0 2 2 2 3 2 输出 2 60 0 1 3 思路: 这道题是经典的综合最短路问题, 综合了 三种最短路方法,1.求…

Flutter AI五子棋

前言 在上一篇文章中,讲解了如何实现双人在本地对战的五子棋,但是只有一个人的时候就不太好玩,同时博主也没有把五子棋相关的文章写过瘾。那么这篇文章,我们来实现一个功能更加丰富的五子棋吧!在设计五子棋的算法方面&…

MongoDB数据库网站网页实例-编程语言Python+Django

程序示例精选 PythonDjangoMongoDB数据库网站网页实例 如需安装运行环境或远程调试,见文章底部个人QQ名片,由专业技术人员远程协助! 前言 这篇博客针对《PythonDjangoMongoDB数据库网站网页实例》编写代码,代码整洁,…

【软件测试】软件测试的基础概念

一、一个优秀的测试人员需要具备的素质 技能方面: 优秀的测试用例设计能力:测试用例设计能力是指,无论对于什么类型的测试,都能够设计出高效的发现缺陷,保证产品质量的优秀测试用例。这就需要我们掌握设计测试用例的方…

【Docker】初识 Docker,Docker 基本命令的使用,Dockerfile 自定义镜像的创建

文章目录 前言:项目部署的挑战一、初识 Docker1.1 什么是 Docker1.2 Docker 与 虚拟机的区别1.3 镜像和容器以及镜像托管平台1.4 Docker的架构解析1.5 Docker 在 CentOS 中的安装 二、Docker 的基本操作2.1 操作 Docker 镜像命令2.1.1 镜像操作相关命令2.1.2 示例一…

C/C++字符函数和字符串函数模拟实现与详解————长度不受限制的字符串函数

个人主页:点我进入主页 专栏分类:C语言初阶 C语言程序设计————KTV C语言小游戏 C语言进阶 C语言刷题 欢迎大家点赞,评论,收藏。 一起努力,一起奔赴大厂。 目录 1.前言 2strlen函数 3.strcpy函数…

通过BeanFactotyPostProcessor动态修改@FeignClient的path

最近项目有个需求,要在启动后,动态修改FeignClient的请求路径,网上找到的基本都是在FeignClient里使用${…},通过配置文件来定义Feign的接口路径,这并不能满足我们的需求 由于某些特殊原因,我们的每个接口…

【算法|动态规划No.9】leetcodeLCR 091. 粉刷房子

个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【手撕算法系列专栏】【LeetCode】 🍔本专栏旨在提高自己算法能力的同时,记录一下自己的学习过程,希望…

2023/10/4 QT实现TCP服务器客户端搭建

服务器端&#xff1a; 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTcpServer> #include <QTcpSocket> #include <QList> #include <QMessageBox> #include <QDebug>QT_BEGIN_NAMESPACE namespace Ui { cla…

便携式脑卒中检测仪是不是离现实不远了?

摘要&#xff1a;本文介绍一个轻量级人工智能的应用--基于tinyML的便携式低成本脑部微波成像系统。参考资料为DOI&#xff1a; 10.1109/ISCAS46773.2023.10181746 医疗资源从来都是稀缺资源。 我们都经历过在医院CT和核磁共振室外排队的情况。即使您万分紧急&#xff0c;也只能…

实验3.2 分期付款计算器

目录 实验目的‪‬‪‬‪‬‪‬‪‬‮‬‭‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‪‬ 实验内容‪‬‪‬‪‬‪‬‪‬‮‬‭‬‪‬‪‬‪‬…