Seata分布式事务模式详细整理
Seata
文章目录
Seata介绍——简单可扩展自治事务框架
Seata支持的事务模型:
- AT模式
- TCC模式
- Saga模式
- XA模式
Seata特性
支持多个微服务框架
dubbo、spring cloud、sofa - rpc、motan和gRPC
高可用
支持基于数据库存储的集群模式,可水平扩展。
高可扩展
支持配置中心、注册中心、spi扩展
Seata框架
组成模块
-
Transaction Manager™
- 事务管理器 - 这是一个产品经理
- 全局事务的发起员,能够根据全局事务的成功与否来对全局提交或回滚进行决策。
- 产品经理决定一个需求,是涉及哪些团队(她会去和各团队leader进行沟通),并且她会对需求做出决策,做,还是不做。
-
Transaction Coordinator(TC)
- 事务协调器 - 技术团队的leader
- 维护全局事务的运行状态,负责协调并驱动全局事务的提交或者回滚
- 技术团队的leader不会决定哪些需求做或者不做,如果做,那么leader会在各团队之间进行协调。
-
Resource Manager(RM)
- 资源管理器 - 各团队的码农们
- 负责对分支事务进行控制,包括分支注册、状态汇报,接收协调器发来的指令,以及对本地事务的开启、提交或回滚。
- 各个团队的coder们,真正完成本项目代码并使之落地的工程建造者,他们会和leader把需求转化成实际代码,也会在leader说不行做不了的时候接收指令回滚掉代码(别吧!)
全局事务
定义
若干分支事务的整体协调
事务流程模型
- TM向TC申请开启一个全局事务,TC创建后,返回全局唯一XID,XID会在全局上下文中传播。
- RM向TC注册分支事务,该分支事务归属于拥有相同XID的全局事务。
- TM想TC发起全局提交或回滚
- TC调度XID的分支事务完成提交或者回滚。
理解:一个全局事务只有一个TM,但是每一个需要调用别的服务的服务也就是全局事务的发起者都应该持有或者说是具备成为一个TM的能力。
从这里我们可以看到,全局事务的发起者通过rpc调用开启远程的分支事务,并且将全局事务id通过调用链传播下去,很明显,这一条调用链只有起点能够对整条调用链的成功与否进行感知,rpc调用链让起点天然具备了决定全局提交或是回滚的能力。
Seata中的事务模式能力边界
条件:
-
数据库一定要支持本地事务——几乎是所有事务模式的基础
-
数据表一定要定义主键——考虑到事务的隔离性,AT里面很有用。
限制:
- 难以实现更高的隔离级别
Seata各模式详细介绍
1. AT模式 —— Seata中的分布式事务最优解
特点
- 无侵入
-
低成本
- 轻依赖 - 编程模型不变,coding时也不需要为分布式事务场景做特定的设计
-
高性能
- 一阶段提交,不阻塞;连接
- 高可用
加粗下划的部分是AT模式最大的价值体现,也是它最大的卖点。
AT的执行流程
两步走 - 2pc在广义上的实现
第一步 - 执行逻辑
- 拦截sql并解析
- ·解析sql语义
- 提取原数据 并将查询结果记录为前镜像
- 执行sql
- 保存后镜像
- 记录undolog(类似于mysql的undolog,这是事务回滚的依据)
- 其实undolog存在的形式是由前镜像与后镜像组成的json
关于为什么要使用undolog的形式问题 ——如果不使用undolog,那么有什么方式能够让已提交的事务进行回滚呢?
答案是使用数据补偿方法,但是如何保证数据补偿方法的幂等性,这一直以来就是个难题。
为什么注册分支在执行sql之后?
因为本地执行事务出现异常,就不需要提交自动可以回滚,那么这个分支事务就没有必要注册到TC中去了。
所以看表象是注册分支这个行为在执行sql之后,但其实本质上是把注册分支这个事尽量后移,让有可能出现问题的地方都先暴露出来,注册分支之后只关注提交事务的状态,让TC能够更加专注。
当然,这样的流程设计并不止是为了流程简洁 ,代码清晰,更加重要的作用,请见下一章节。
第二步
- TC向所有RM发起提交/回滚
- 提交 - 清理快照数据(undolog)
回顾:如果分支事务已经提交,Seata是如何回滚的?
根据前后镜像来进行回滚。
提到回滚,那必定要提到另一个概念,事务隔离。那么Seata是怎么保证事务之间进行隔离的呢?
AT模式的回滚细节与事务隔离
事务隔离问题出现的可能性
假设本地事务A注册完分支之后即提交了,对于其他的本地事务B\C\D来说,并不知道A的分布式事务其实尚未完成,那么就存在对已提交的A事务的数据进行更改的可能性。
解决方法
“行锁”。
在上面的例子中,Seata的RM在为A在TC中注册分支事务时,会将行的唯一标识也一并带上,在TC中注册,而TC会控制非A事务无法对此行进行修改。
在“行锁”解决方案中,行的唯一标识是至关重要的存在,而主键则完美的符合了行锁的唯一标识的需求。
多说一句,这样的“行锁”与行锁本身的设计还是挺像的,就像是在说“数据库的不顶用了,那我们就自己来造一个”。
附带一个undolog的范本:
AT回滚操作步骤
*:以下步骤都是基于存于本地数据库的undolog记录实现的
- 校验脏写
- 新镜像与当前数据库数据比较
- 此情况只会因为不受AT管理的数据操作而出现,比如直接对数据库相关字段进行更改、或是本地事务没有注册到TC上
- 这种情况需要发出警报,在业务实现的时候也要注意。
- 还原数据
- 根据前后镜像生成逆向sql
- 删除中间数据
- 删除前后镜像
AT模式中的事务隔离级别
一、写隔离:
背景:事务A和B需要对同一行进行更改,并且,AB都交由TC管理。
当事务A拿到全局锁之后,事务B可以在本地执行业务操作,但是当事务B获取全局锁时,需要先等待A释放全局锁才行,
若B等待超时,则事务B会回滚本地事务,释放本地锁;
-
写的特殊情况:1.事务A 获得了全局锁并在本地提交。2.事务B获取了本地锁并对全局锁进行等待。3.A发生了异常,需要进行全局回滚。
特殊情况发生原因:A需要回滚,而回滚需要获取本地锁,然而本地锁已经被B持有了,形成了互相等待的死锁局面。
这种情况下,根据刚才的流程,事务B在等待全局锁的时候会因为获取锁超时而回滚本地事务,当事务B回滚并释放锁后,事务A 获得本地锁,进行回滚。
二、读隔离:
背景:事务A全局锁住了某一行但是已经本地提交,而事务B需要对同一行进行读取,并且,AB都交由TC管理。
- 读未提交(这是默认配置,实际项目操作的时候一定要对业务进行分析进行针对性更改)
- 正常读取即可,但是显然,这有可能导致脏读的出现
- 读已提交
- 根据全局锁来进行判断
- 底层实现原理是对“select … for update;”语句做代理,以在全局层面对行请求锁来进行读取。、
在官方文档中,“读已提交”是在“特定的场景下”才必须要求的,我们在使用的时候一定要对业务进行分析,斟酌地进行读隔离的配置。
2. TCC模式
TCC模式的想法是好的,但是实际在业务中操作却有一定的难度,在各方面都不如AT模式,故目前了解即可。
传统TCC模式
- try - 预留资源
- confirm - 使用预留资源
- cancel - 取消,释放预留资源
try的目的就是为了保证confirm能够成功
业务只需要关注try,即预留资源的工作,保证业务能够成功,事务的部分Seata的TC会自动完成
Seata TCC实现原理
TCC模式需要注意的点
-
允许空回滚
try阶段结果信息可能无法顺利到达TC,之后触发回滚。
-
防悬挂控制
try阶段的结果信息到的太晚了,已经触发了回滚操作。
需要拒绝已经回滚的try。
-
需要幂等
可能会有超时重试、补偿,try、confirm、cancel都有可能被重复调用
这部分我就不多讲了,因为“需要幂等”这一条就算尝试去实现其实也是有风险的,业务上肯定是不如AT模式的。