什么是Seata?
官网定义:Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Seata三要素
TC - 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM - 事务管理器:定义全局事务的范围 ---- 开始全局事务、提交或回滚全局事务。
RM - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
什么是Seata AT模式?
定义
总结一下:Seata AT模式是两阶段提交协议的演变。第一阶段,基于JDBC数据源代理通过对业务SQL进行解析,把业务数据在更新前后的数据镜像组织成回滚日志,将业务数据的更新和回滚日志的写入通过同一本地事务进行提交,利用本地事务ACID特性保证任何业务数据的更新一定有对应的回滚日志。第二阶段异步清理回滚日志或者根据回滚日志进行业务数据的回滚操作。
第一阶段的重点就是完成分支事务的注册与提交,保证业务数据的更新与对应回滚日志的一致性,其根本目的是为第二阶段可能的回滚操作提供数据支撑。实质上,第一阶段结束,本地事务已经完成提交,业务数据的更新已经写入数据库,因此在第二阶段,如果全局事务结果是提交,那么对于分支事务系统而言,仅仅只需要根据全局事务ID和分支事务ID找到对应undo_log并删除即可:
如果第二阶段全局决议结果是回滚,则需要根据XID和branch ID找到对应的undo_log记录,并根据记录生成反向的更新SQL并执行,完成分支事务的回滚:
AT模式的隔离性实现
从上面的流程可以看出,AT模式在第一阶段完成后实质上已经完成了本地事务的提交,那么当数据库事务隔离级别为读已提交及以上时,在全局事务完成提交前,无法保证分支事务提交数据的不可读及不可写。那么,AT模式是如何解决隔离性的问题呢?
Seata 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 语句。
Seata 的全局锁是由 TC 也就是 server 来集中维护,而不是在数据库维护的。
这样做有两点好处:
- 一方面:锁的释放非常快,尤其是在全局提交的情况下,收到全局提交的请求,锁马上就释放掉了,不需要与 RM 或数据库进行一轮交互。
- 另外一方面:因为锁不是数据库维护的,从数据库层面看,数据没有锁定。这也就是给极端情况下,业务 降级 提供了方便,事务协调器异常导致的一部分异常事务,不会 block 后面业务的继续进行。
然而,坏处是,Seata无法保证数据不被全局事务之外的其他操作进行修改。
AT模式相对于2PC的改进
传统的2PC模式中,所有参与节点都是事务阻塞型的,事务节点对资源的占有持续于整个全局事务期间,严重影响系统性能,并且一旦事务协调者出现问题问题,事务参与者会一直阻塞下去,导致整个业务系统出现问题。
AT模式下所有分支事务系统选择在第一阶段执行完毕立即释放本地锁和链接资源,以本地事务的形式实现分支系统业务数据的更新,避免长时间占有公共数据资源,保证了系统的吞吐量。同时,通过TC维护全局锁,避免了当事务协调器出现问题时导致的整个业务系统的阻塞。
AT模式的优缺点
优点:
1、低成本,业务无侵入,实现简单
2、高性能,协议不阻塞,资源释放快,保障业务的吞吐
3、高可用:极端的异常情况下,可以暂时 跳过异常事务,保证整个业务系统的高可用
缺点:
1、SDK依赖较重,需要搭建TC服务器,可用性受限于TC集群
2、严重依赖本地事务,数据库本身必须支持本地事务
3、隔离性依赖TC,无法保证高隔离性
官网示例:
以一个示例来说明整个 AT 分支的工作过程。
业务表:product
Field | Type | Key |
---|---|---|
id | bigint(20) | PRI |
name | varchar(100) | |
since | varchar(100) |
AT 分支事务的业务逻辑:
update product set name = 'GTS' where name = 'TXC';
一阶段
过程:
- 解析 SQL:得到 SQL 的类型(UPDATE),表(product),条件(where name = 'TXC')等相关的信息。
- 查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据。
select id, name, since from product where name = 'TXC';
得到前镜像:
id | name | since |
---|---|---|
1 | TXC | 2014 |
- 执行业务 SQL:更新这条记录的 name 为 'GTS'。
- 查询后镜像:根据前镜像的结果,通过 主键 定位数据。
select id, name, since from product where id = 1`;
得到后镜像:
id | name | since |
---|---|---|
1 | GTS | 2014 |
- 插入回滚日志:把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到
UNDO_LOG
表中。
{
"branchId": 641789253,
"undoItems": [{
"afterImage": {
"rows": [{
"fields": [{
"name": "id",
"type": 4,
"value": 1
}, {
"name": "name",
"type": 12,
"value": "GTS"
}, {
"name": "since",
"type": 12,
"value": "2014"
}]
}],
"tableName": "product"
},
"beforeImage": {
"rows": [{
"fields": [{
"name": "id",
"type": 4,
"value": 1
}, {
"name": "name",
"type": 12,
"value": "TXC"
}, {
"name": "since",
"type": 12,
"value": "2014"
}]
}],
"tableName": "product"
},
"sqlType": "UPDATE"
}],
"xid": "xid:xxx"
}
- 提交前,向 TC 注册分支:申请
product
表中,主键值等于 1 的记录的 全局锁 。 - 本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。
- 将本地事务提交的结果上报给 TC。
二阶段-回滚
- 收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。
- 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
- 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理,详细的说明在另外的文档中介绍。
- 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:
update product set name = 'TXC' where id = 1;
- 提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。
二阶段-提交
- 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
- 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。
什么是Seata TCC模式?
TCC模式是指支持把 自定义 的分支事务纳入到全局事务的管理中的事务处理模式,是与AT模式相对的,不依赖于底层数据资源的事务支持:
- 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
- 二阶段 commit 行为:调用 自定义 的 commit 逻辑。
- 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。
后续更新。。。
什么是Seata Saga模式?
Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。
后续更新。。。
参考资料:
http://seata.io/zh-cn/docs/overview/what-is-seata.html
https://blog.csdn.net/hosaos/article/details/89403552
https://www.jianshu.com/p/0ed828c2019a