官方文档:Seata 是什么
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Seata术语
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
负责通知命令的中间件Seata-Server
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
决定什么时候全局提交、回滚
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
真正操作每一个数据库的数据
Seata提供了四种不同的分布式事务解决方案
- XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
- AT模式:最终一致的分阶段事务模式,无业务侵入(默认模式)
- TCC模式:最终一致的分阶段事务模式,有业务侵入
- SAGA模式:长事务模式,有业务入侵
将需要两个阶段完成分布式事务处理的操作,称为二阶段提交
(不同厂商所提出的解决方案不同)
1、业务场景
2、操作流程
分布式事务二阶段提交处理过程 (操作请求由事务协调着发出)
- 事务协调者向各个服务发起数据操作的请求,开启全局事务
- 每一个服务,sql操作完,都不进行提交(未提交状态)
- 当所有模块,都向事务协调者通知,自己已经执行完成
- 事务协调者额外的再次向各模块发出通知,可以执行事务提交
XA规范是分布式事务处理标准,它描述了全局的TM和局部的RM之间的接口,几乎所有的主流的数据库都对XA规范提供了支持;
执行原理:
XA将分布式事务分为两个阶段,一个是准备阶段,一个是执行阶段。
- 准备阶段: 事务协调者会向事务参与者RM发送一个请求,这里的RM其实是由数据库实现的,所以可以认为RM就是数据库。让数据库去执行事务,但执行完不要提交,而是把结果告知事务协调者
- 执行阶段: 事务协调者根据结果,通知RM回滚或者提交事务
XA模式优点:
这是一种强一致性的解决方案,因为每一个微服务都是基于各自的事务的,各自的事务是满足ACID的,而且等到大家都执行完了且都成功了才提交,所以全局事务是满足ACID的。
实现比较简单,因为很多数据库都实现了这种模式,使用Seata的XA模式只需要简单的封装上TM。
XA模式缺点:
第一阶段不提交,等到第二阶段再提交,但是等的过程中要占用数据库锁,如果一个分布式事务中跨越了很多个分支事务,则可能造成很多资源的浪费,使得别的请求无法访问,降低了可用性;
依赖于数据库,对于如果有的数据库没有实现这种模式,则无法使用这个模式来实现分布式事务。
每个RM中有个undo_log表(数据快照),里面会记录一些数据,比如逆向sql,用于还原
执行原理:
- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交, 提交异步化,非常快速地完成,释放本地锁和连接资源。
- 二阶段:回滚通过一阶段的回滚日志进行反向补偿
AT模式于XA模式的区别:
AT模式 | XA模式 |
---|---|
第一阶段直接提交,不锁定资源 | 第一阶段不提交事务,会锁定资源 |
TC上的全局锁:只是记录操作某张表的某行的全局事务,是由Seata管理的,对于不是Seata管理的相关事务,依旧可以直接操作数据表。比如由其他业务要修改同张表同行数据的其他字段,这个时候是可以的 | XA中事务不提交,占用的是数据库的锁,所有的crud的操作都会被限制 |
利用数据快照实现数据回滚 | 依赖数据库机制实现回滚 |
最终一致 | 强一致 |
AT模式优点:
AT模式缺点:
以一个示例来说明:
两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。
tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。 tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁 。
tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。
如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。
此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。
因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。
在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted) 。
如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。
SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。
出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句。