分布式事务·入门与解决·壹
文章目录
- 1 基础理论
- 1.1 本地事务
- 1.2.分布式事务
- 1.3 CAP定理
- 2.2 CAP的一种解决思想——BASE理论
- 2.3.分布式事务解决思路AP、CP、TC
- 2 分布式事务的一种解决方案——Seata
- 2.1 Seata的架构
- 2.2 部署TC服务
- 2.3.微服务集成Seata
- 2.3.1.引入依赖
- 2.3.2 配置TC地址
- 2.3.3 微服务如何根据配置寻找TC地址?
- 2.3.4 报错jdbc:java.lang.NoSuchMethodException: com.mysql.cj.conf.PropertySet.getBooleanReadableProperty......解决方案
- 3 Seata四种事务模式验证
- 3.1 XA规范
- 3.1.1 两阶段提交
- 3.1.2 Seata的XA模型实现
- 3.1.3 优缺点
- 3.1.4 实现XA模式
- 3.1.4.1 每个微服务application.yml中开启XA模式
- 3.1.4.2 全局事务入口方法添加@GlobalTransactional注解
- 3.1.4.3 postman测试
- 3.2 AT模式
- 3.2.1 Seata的AT模型
- 3.2.2 业务引入
- 3.2.3 AT与XA的区别
- 3.2.4 脏写问题
- 3.2.5 AT模式优缺点
- 3.2.6 AT模式实现
- 3.3 TCC模式
- 3.3.1 TCC模式的业务实现
- 3.3.2 Seata的TCC模型
- 3.3.3 TCC模型优缺点
- 3.3.4 事务悬挂和空回滚
- 3.3.4.1 空回滚
- 3.3.4.2 业务悬挂
1 基础理论
1.1 本地事务
事务四大特性(ACID):事务的A(Atomicity)
原子性
、C(Consistency)一致性
、I(Isolation)隔离性
、D(Durability)持久性
例如:单体项目中同时操作多张表,那么必须添加
@Transactional
开启事务注解。因而本地事务也称之为单机事务
。
1.2.分布式事务
分布式事务:由多个服务或数据库架构产生的事务。
案例:下单付款
当把三件事看做一个事务,则相关操作需满足事务原子性“要么全部成功,要么全部失败”,此类种种称为:分布式系统下的事务
1.3 CAP定理
CAP定理中:三个指标不可能同时做到
CAP定理中:为啥三个指标不可能同时做到?
假设N1、N2通信是网络突然断开,当用户给N2发送数据,数据需要经过N1与N2之间的链路通信,此时两种解决思路:
①牺牲数据的一致性
,响应旧的数据给用户;
②牺牲可用性
,阻塞等待,直至网络通信恢复,再响应给用户。
参考:分布式CAP定理,为什么不能同时满足三个特性?
指标 | 解释 |
---|---|
C (Consistency)一致性 | 用户访问分布式系统中的任意节点,得到的数据必须一致。 |
A(Availability) 可用性 | 用户访问集群中的任意健康节点,必须能得到响应,而不是超时或拒绝。 |
P(Partition tolerance ) 分区容错性 | 因为网络故障或其它原因导致分布式系统中的部分节点与其它节点失去连接,形成独立分区。 |
2.2 CAP的一种解决思想——BASE理论
思想 | 解释 |
---|---|
Basically Available(基本可用 ) | 分布式系统在出现故障时,允许损失部分可用性,即保证核心业务可用 |
Soft State(软状态 ) | 在一定时间内,允许出现中间状态,比如临时的不一致状态 |
Eventually Consistent(最终一致性 ) | 在软状态结束后,最终达到数据一致 |
2.3.分布式事务解决思路AP、CP、TC
模式 | 解释 |
---|---|
CP模式 | 各子事务执行后互相等待,同时提交,同时回滚,达成强一致 ,等待过程中,处于弱可用状态。 |
AP模式 | 各子事务分别执行和提交,过程中允许出现结果不一致(软状态 ),最终需采用弥补措施,实现数据的最终一致性 |
TC(事务协调者 ) | CP、AP模式中子事务间互相通讯,协调事务状态的协调者 |
子系统事务
又叫分支事务。- 有关联的分支组成一体成称为全局事务。
2 分布式事务的一种解决方案——Seata
官网地址:http://seata.io/
2.1 Seata的架构
角色 | 功能 |
---|---|
TC (Transaction Coordinator) 事务协调者 | 维护全局和分支的状态,协调全局事务提交或回滚 |
RM (Resource Manager) 资源管理器、事务参与者 | 管理分支事务资源,与TC通信以注册分支事务 和报告分支事务状态 ,驱动分支事务提交或回滚 |
TM (Transaction Manager) 事务管理器 | 定义全局事务范围、开始全局事务、提交或回滚全局事务 |
整体的架构图:
Seata基于上述架构提供四种不同分布式事务解决方案,注意:TC事务协调者为无论哪种模式的必须 :
解决方案 | 解释 |
---|---|
XA模式 | 强一致性分阶段事务模式 ,牺牲一定可用性,无业务侵入 |
TCC模式 | 最终一致性分阶段事务模式 ,有业务侵入 |
AT模式 | 最终一致性分阶段事务模式 无业务侵入,Seata默认模式 |
SAGA模式 | 长事务模式 ,有业务侵入 |
2.2 部署TC服务
参阅: seata的部署和集成
2.3.微服务集成Seata
2.3.1.引入依赖
由于大多数SpringCloud版本自带seata驱动版本太低,我们需要排除自带依赖。
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<!--版本较低,1.3.0,因此排除-->
<exclusion>
<artifactId>seata-spring-boot-starter</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<!--seata starter 采用1.4.2版本-->
<version>${seata.version}</version>
</dependency>
2.3.2 配置TC地址
在每个分布式服务application.yml中,配置TC服务信息,通过注册中心nacos,结合服务名称获取TC地址:
seata: registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址 type: nacos # 注册中心类型 nacos nacos: server-addr: 127.0.0.1:8848 # nacos地址 namespace: "" # namespace,默认为空 group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP application: seata-tc-server # seata服务名称 username: nacos password: nacos tx-service-group: seata-demo # 事务组名称 service: vgroup-mapping: # 事务组与cluster的映射关系 seata-demo: SH
2.3.3 微服务如何根据配置寻找TC地址?
注册到Nacos中的微服务,确定一个具体实例需四个信息:
- namespace为空,就是默认的public
- 综上,TC服务信息:public@DEFAULT_GROUP@seata-tc-server@SH,据此去Nacos拉取对应的实例信息。
属性 | 解释 |
---|---|
namespace | 命名空间 |
group | 分组 |
application | 服务名 |
cluster | 集群名 |
2.3.4 报错jdbc:java.lang.NoSuchMethodException: com.mysql.cj.conf.PropertySet.getBooleanReadableProperty…解决方案
报错原因:seata-server-1.4.2开发时候MySQL只更新到8.0.11,因此MySQL数据库只支持版本≤ 8.0.11
解决方案:
不用
卸载更换MySQL版本,只需要把数据库驱动
≤8.0.11
3 Seata四种事务模式验证
3.1 XA规范
XA 规范: X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,描述了
TM(全局事务范围,开始、提交或回滚全局事务)
与RM(管理分支事务资源,在TC上注册事务分支、报告分支状态最终驱动分支事务提交或回滚)
间接口,主流的数据库大部分都支持 XA 规范。
3.1.1 两阶段提交
实现原理两阶段提交:
正常情况:
一阶段:
- TC(事务协调者)通知每个RM(事务参与者)执行本地事务(单机/单体事务)。
- 本地事务执行完成后报告事务执行状态给TC,此时事务不提交,继续持有数据库锁(联想多线程操作同一资源)。
异常情况:
二阶段:
- TC基于一阶段的报告判断下一步操作
- ①若一阶段都成功,通知所有RM(事务参与者)
提交事务
(数据库CRUD)。- ②若一阶段任意一个TC失败,通知所有RM
回滚事务
。
3.1.2 Seata的XA模型实现
Seata对原始XA模式进行简单的封装和改造:
RM(事务参与者)一阶段的工作:
①注册分支事务到TC
②执行分支业务sql但不提交
③报告执行状态到TCTC二阶段工作:
TC检测各分支事务执行状态
①若都成功,通知所有RM提交事务
②若有失败,通知所有RM回滚事务RM二阶段工作:
接收TC指令,提交或回滚事务
3.1.3 优缺点
XA模式的优点:
- 事务的强一致性,满足ACID原则。
- 常用数据库都支持,实现简单,无代码侵入
XA模式的缺点:
- 一阶段需锁定数据库资源,等待二阶段结束才释放,性能较差
- 依赖关系型数据库实现事务
3.1.4 实现XA模式
3.1.4.1 每个微服务application.yml中开启XA模式
seata:
data-source-proxy-mode: XA
3.1.4.2 全局事务入口方法添加@GlobalTransactional注解
3.1.4.3 postman测试
3.2 AT模式
AT模式也是分阶段提交事务模型,其弥补XA模型组资源锁定周期过长缺陷。
3.2.1 Seata的AT模型
基本流程图:
阶段一RM的工作:
①注册分支事务
②记录undo-log(数据快照)
③报告事务状态阶段二提交时RM工作:
- 删除undo-log即可
阶段二回滚时RM工作:
- 根据undo-log恢复数据到更新前
3.2.2 业务引入
已有条件:
数据库表记录用户余额:
此业务将要执行的业务sql:update tb_account set money = money - 10 where id = 1
id | money |
---|---|
1 | 100 |
AT模式下,当前业务流程:
一阶段:
①TM(事务管理器)注册全局事务到TC
②TM调用分支事务(子系统事务)
③分支事务准备执行业务SQL
④RM拦截业务sql,根据where条件查询原始数据,形成快照。{ "id": 1, "money": 100 }
⑤RM执行业务sql,提交本地事务,释放数据库锁。此时money = 90
⑥RM报告本地事务状态给TC二阶段:
①TM通知TC事务结束
TC检查分支事务状态
①若都成功:则立即删除快照
②若分支事务存在失败,读取快照数据({"id": 1, "money": 100}
),将快照恢复到数据库,完成数据回滚
。
3.2.3 AT与XA的区别
AT | XA |
---|---|
AT一阶段直接提交,不锁定资源 | XA一阶段不提交事务 |
AT模式利用数据快照模式实现数据回滚 | XA依赖数据库机制ACID ,实现回滚 |
AT模式最终一致 | XA模式强一致 |
3.2.4 脏写问题
多线程并发访问AT模式,有可能出现脏写问题:
即:事务1、事务2两线程同时操作同一数据库资源,事务2完成相关操作数据是80,然而事务1完成相关业务最终回滚到数据原始数据100,此时事务1的数据
覆盖掉事务2的处理后的数据
,这造成数据不一致,出现脏数据
。
解决思路:
全局锁
。即:释放DB锁前,先拿到全局锁。避免同一时刻另外一个事务操作当前数据即:事务1、事务2两线程同时操作同一数据库资源
①事务一开始业务sql时保存当前数据库起始快照(DB快照)
②然后执行业务sql完成后,再获取数据库快照(全局锁)
③同时刻,事务2执行业务sql前获取到业务1修改过的快照{"id": 1, "money": 90}
,执行完业务sql后,再保存全局锁,然而此时全局锁,被业务1占有,业务2默认重试30次间隔10毫秒,解决不了,获取不到全局锁(被业务1占有),此时任务超时回滚并释放DB锁
④在业务一方面,直接锁一直持有,后继凭借业务1操作数据库前后快照,添加给人工,完成处理。
3.2.5 AT模式优缺点
优点 | 缺点 |
---|---|
一阶段提交事务,释放数据库资源,性能较好 | 两阶段属于软状态,属于最终一致 |
利用全局锁实现读写隔离 | 框架快照影响性能,但较XA模式要好 |
无代码侵入,框架自动完成回滚、提交 |
3.2.6 AT模式实现
3.3 TCC模式
TCC模式与AT模式相似,每阶段都是独立事务,但是TCC需要人工编码实现数据恢复。需要实现三个方法:
方法 | 解释 | 联想 | 每阶段做什么 |
---|---|---|---|
Try | 资源的检测和预留 | 锁定金额 | 资源检查和预留 |
Confirm | 完成资源操作业务,要求Try成功,Confirm也一定要成功 | 提交 | 业务执行和提交 |
Cancel | 预留资源释放,可以理解为Try的反向操作 | 回滚 | 预留资源的释放 |
3.3.1 TCC模式的业务实现
-
阶段一( Try ):检查余额是否充足,如果充足则冻结金额增加30元,可用余额扣除30
-
阶段二(Confirm):假如要提(Confirm),则冻结金额扣减30确认可以提交,不过之前可用金额已经扣减过了,这里只要清除冻结金额就好了:
-
阶段三(Canncel):如果要回滚(Cancel),则冻结金额扣减30,可用余额增加30需要回滚,那么就要释放冻结金额,恢复可用金额:
3.3.2 Seata的TCC模型
Seata中TCC模型依然延续之前事务架构:
3.3.3 TCC模型优缺点
TCC的优点是什么?
- 一阶段完成直接提交事务,释放数据库资源,性能好
- 相比AT模型,无需生成快照,无需使用全局锁,性能最强
- 不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库
TCC的缺点是什么?
- 有代码侵入,需要人为编写try、Confirm和Cancel接口,太麻烦
- 软状态,事务是最终一致
- 需要考虑Confirm和Cancel的失败情况,做好幂等处理
3.3.4 事务悬挂和空回滚
3.3.4.1 空回滚
当某分支事务的try阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作,这时cancel不能做回滚,就是空回滚。执行cancel操作时,应当判断try是否已经执行,如果尚未执行,则应该空回滚。
3.3.4.2 业务悬挂
于已经空回滚的业务,之前被阻塞的try操作恢复,继续执行try,就永远不可能confirm或cancel ,事务一直处于中间状态,这就是业务悬挂。执行try操作时,应当判断cancel是否已经执行过了,如果已经执行,应当阻止空回滚后的try操作,避免悬挂