接上一篇文章:Sentinel 控制台规则持久化。从本节内容开始,我们来了解一个全新的产物:Spring Cloud Alibaba Seata 分布式事务
。开篇先附上部分 Seata 相关内容:
学习到Seata,我默认大家对事务已经有了一定的了解,关于事务此处就不过多介绍。在之前 单机单库
环境下,针对事务的处理还是比较简单的。尤其是结合 Spring 框架,可以说是一个@Transaction 注解走天下。事务 & Spring 事务相关内容,点击链接去了解吧:事务 & Spring 事务内容介绍
在如今 Spring Cloud 分布式微服务架构体系中,按业务模块划分,一个模块使用一个数据库。多个模块配合来完成一个业务,我们就从 官网 的一个微服务实例开始吧。
用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:
单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局数据一致性问题是无法保证的。救世主 Seata 它来了。
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。
Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双11,对各部门业务进行了有力的支撑
。经过多年沉淀与积累,商业化产品先后在阿里云、金融云进行售卖。2019.1 为了打造更加完善的技术生态和普惠技术成果,Seata 正式宣布对外开源,未来 Seata 将以社区共建的形式帮助其技术更加可靠与完备。
此处重点介绍 AT模式
,在工作中也最常用,用起来也比较简单。
(术语即:名词介绍,以下内容摘自:Seata 官方文档 http://seata.io/zh-cn/docs/overview/terminology.html)
Seata管理的分布式事务的典型生命周期:
(重点介绍AT模式,使用简单,最常用。以下内容摘自:Seata 官方文档 http://seata.io/zh-cn/docs/overview/what-is-seata.html,此处简单介绍一下 AT模式,其他模式参考官网)
两阶段(2PC)提交协议的演变:
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
二阶段:
1.提交异步化,非常快速地完成。
2.回滚通过一阶段的回滚日志进行反向补偿。
以一个示例来说明:
两个全局事务 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 语句。
以一个示例来说明整个 AT 分支的工作过程。
业务表:product
Field | Type | Key |
---|---|---|
id | bigint(20) | PRIMARY |
name | varchar(100) | |
since | varchar(100) |
AT 分支事务的业务逻辑:
update product set name =
'GTS'
where name ='TXC'
;
过程:
select id, name, since from product where name =
'TXC'
;
得到前镜像:
id | name | since |
---|---|---|
1 | TXC |
select id, name, since from product where id = 1;
得到后镜像:
id | name | since |
---|---|---|
1 | GTS | 2014 |
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"
}
product
表中,主键值等于 1 的记录的 全局锁 。update product set name =
'TXC'
where id = 1;
Seata 1.3.0 2020.07.14 发布,截止今天 2020.07.30 已半月有余,关于 Seata 官方部署 新人文档 无力吐槽,Seata 老版本学过来的部署比较简单,初学者直接部署 1.x 以上版本好困难的说,我研究了2天才全套部署成功,以博客的方式分享给大家。
下一篇:Spring Cloud Alibaba Seata 1.3.0 部署
博主写作不易,加个关注呗
求关注、求点赞,加个关注不迷路 ヾ(◍°∇°◍)ノ゙
我不能保证所写的内容都正确,但是可以保证不复制、不粘贴。保证每一句话、每一行代码都是亲手敲过的,错误也请指出,望轻喷 Thanks♪(・ω・)ノ