文章目录
概述
确切来说seata at是xa的实现,并且进行了改进。
一、分布式事务产生得原因:
1.1、数据库分库分表
当数据库单表一年产生的数据超过1000W,那么就要考虑分库分表,具体分库分表的原理在此不做解释,以后有空详细说,简单的说就是原来的一个数据库变成了多个数据库。这时候,如果一个操作既访问01库,又访问02库,而且要保证数据的一致性,那么就要用到分布式事务
1.2应用SOA化
所谓的SOA化,就是业务的服务化。比如原来单机支撑了整个电商网站,现在对整个网站进行拆解,分离出了订单中心、用户中心、库存中心。对于订单中心,有专门的数据库存储订单信息,用户中心也有专门的数据库存储用户信息,库存中心也会有专门的数据库存储库存信息。这时候如果要同时对订单和库存进行操作,那么就会涉及到订单数据库和库存数据库,为了保证数据一致性,就需要用到分布式事务。
二、分布式事务解决方案XA模式
XA是一个分布式事务协议,由Tuxedo提出。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。XA实现分布式事务的原理如下:
在第一阶段:TM会发送 Prepare 到所有参与分布式事务的RM询问是否可以提交操作,参与分布式事务的所有RM接收到请求,实现自身事务提交前的准备工作并返回结果。根据RM返回的结果,如果涉及分布式事务的所有RM都返回可以提交。
在第二阶段:则TM给RM发送commit的命令,每个RM实现自己的提交同时释放锁和资源,然后RM反馈提交成功,TM完成整个分布式事务;如果任何一个RM返回不能提交,则涉及分布式事务的所有RM都被告知需要回滚。MySQL XA 也是基于这个规范实现的
,接下来我们介绍下MySQL XA。
总的来说,XA协议比较简单,而且一旦商业数据库实现了XA协议,使用分布式事务的成本也比较低。但是,XA也有致命的缺点,那就是性能不理想
,特别是在交易下单链路,往往并发量很高,XA无法满足高并发场景。XA目前在商业数据库支持的比较理想,在mysql数据库中支持的不太理想,mysql的XA实现,没有记录prepare阶段日志,主备切换回导致主库与备库数据不一致。许多nosql也没有支持XA,这让XA的应用场景变得非常狭隘。
其实也并非不用,例如在IBM大型机上基于CICS很多跨资源是基于XA协议实现的分布式事务,事实上XA也算分布式事务处理的规范了,但在为什么互联网中很少使用,究其原因有以下几个:
- 性能(阻塞性协议,增加响应时间、锁时间、死锁);
- 数据库支持完善度(MySQL 5.7之前都有缺陷);
- 协调者依赖独立的J2EE中间件(早期重量级Weblogic、Jboss、后期轻量级Atomikos、Narayana和Bitronix);
- 运维复杂,DBA缺少这方面经验;
- 并不是所有资源都支持XA协议;
准确讲XA是一个规范、协议,它只是定义了一系列的接口,只是目前大多数实现XA的都是数据库或者MQ,所以提起XA往往多指基于资源层的底层分布式事务解决方案。其实现在也有些数据分片框架或者中间件也支持XA协议,毕竟它的兼容性、普遍性更好。
三、Seata AT(TXC) 模式
3.1基本概念
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
Seata 的全局事务处理过程,分为两个阶段:
- 执行阶段:执行分支事务,并保证执行结果满足是可回滚的(Rollbackable)和持久化的(Durable)。
- 可回滚:根据 SQL 解析结果,记录回滚日志
- 持久化:回滚日志和业务 SQL 在同一个本地事务中提交到数据库
- 完成阶段:根据执行阶段 结果形成的决议,应用通过 TM 发出的全局提交或回滚的请求给 TC,TC 命令 RM 驱动分支事务进行 Commit 或 Rollback。
- 分支提交:异步删除回滚日志记录
- 分支回滚:依据回滚日志进行反向补偿更新
Seata 的所谓事务模式 是指:运行在 Seata 全局事务框架下的分支事务的行为模式,准确地讲应该叫作分支事务模式。
不同的事务模式区别在于分支事务使用不同的方式达到全局事务两个阶段的目标。
回答以下两个问题
执行阶段 :如何执行并保证 执行结果满足是可回滚的(Rollbackable)和持久化的(Durable)。
完成阶段: 收到TC的命令后,做到事务的回滚/提交
3.2AT模式工作流程
3.2.1第一阶段:
在Seata的组件中,如果你想开启分布式事务,那么就应该在你的业务入口或者事务发起入口加上@GlobalTransactional注解。数据源被代理后,通过被DataSourceProxy代理后,所执行的sql会被提取,解析,保存前镜像后,再执行业务sql,再保存后镜像,以便与后续出现异常,进行二阶段的回滚操作。
3.2.2第二阶段:
AT 模式二阶段提交
二阶段如果是提交的话,因为“业务 SQL”在一阶段已经提交至数据库,所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
AT 模式二阶段回滚
二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。
回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
3.2.3:AT模式读写隔离实现方式
http://seata.io/zh-cn/docs/dev/mode/at-mode.html
3.2.4:完整的AT在Seata所制定的事务模式下的模型图
3.2.5:Seata AT模式优劣:
①相比与其它分布式事务框架,Seata架构的亮点主要有几个:
-
应用层基于SQL解析实现了自动补偿,从而最大程度的降低业务侵入性;
-
将分布式事务中TC(事务协调者)独立部署,负责事务的注册、回滚;
-
通过全局锁实现了写隔离与读隔离。
②性能损耗(纯内存运算类的忽略不计):
一条Update的SQL,则需要全局事务xid获取(与TC通讯)、before image(解析SQL,查询一次数据库)、after image(查询一次数据库)、insert undo log(写一次数据库)、before commit(与TC通讯,判断锁冲突),这些操作都需要一次远程通讯RPC,而且是同步的。
另外undo log写入时blob字段的插入性能也是不高的。每条写SQL都会增加这么多开销,粗略估计会增加5倍响应时间(二阶段虽然是异步的,但其实也会占用系统资源,网络、线程、数据库)。
③补偿型事务模式的问题:
本质上,Seata 已经支持的 3 大事务模式:AT、TCC、Saga 都是 补偿型的。
补偿型事务处理机制构建在事务资源 之上(要么在中间件层面,要么在应用层面),事务资源本身对分布式事务是无感知的。
事务资源对分布式事务的无感知存在一个根本性的问题:无法做到真正的全局一致性 。
比如,一条库存记录处在补偿型 事务处理过程中由 100 扣减为 50。此时,仓库管理员连接数据库查询统计库存,就看到当前的 50之后事务因为异外回滚,库存会被补偿回滚为 100。显然,仓库管理员查询统计到的 50 就是脏数据
。
可以看到,补偿型分布式事务机制因为不要求事务资源本身(如数据库)的机制参与,所以无法保证从事务框架之外的全局视角的数据一致性。
④XA 的价值:
因为事务资源感知并参与分布式事务处理过程,所以事务资源(如数据库)可以保障从任意视角对数据的访问有效隔离,满足全局数据一致性。
比如,上一节提到的库存更新场景,XA 事务处理过程中,中间态数据库存 50 由数据库本身保证,是不会仓库管理员的查询统计看到的。(当然隔离级别需要读已提交以上)
与同为业务无侵入 的 AT 模式比较:
首先,因为同样运行在 Seata 定义的分布式事务框架下,XA 模式并没有产生更多事务协调的通信开销。
其次,并发事务间如果数据存在热点,产生锁冲突,这种情况在 AT 模式(默认使用全局锁)下同样存在的。
所以,在影响性能的两个主要方面,XA 模式并不比 AT 模式有非常明显的劣势
。AT 模式性能优势主要在于:集中管理全局数据锁,锁的释放不需要 RM 参与,释放锁非常快;另外,全局提交的事务完成阶段异步化。
3.2.6 与 XA 的差别在什么地方?
架构层次
XA 方案的 RM 实际上是在数据库层,RM 本质上就是数据库自身(通过提供支持 XA 的驱动程序来供应用使用)。
而 Fescar 的 RM 是以二方包的形式作为中间件层部署在应用程序这一侧的,不依赖与数据库本身对协议的支持,当然也不需要数据库支持 XA 协议。这点对于微服务化的架构来说是非常重要的:应用层不需要为本地事务和分布式事务两类不同场景来适配两套不同的数据库驱动。
这个设计,剥离了分布式事务方案对数据库在 协议支持 上的要求。
也就是说即使数据库不支持xa协议,也可以使用seata来实现xa特性。
两阶段提交
先来看一下 XA 的 2PC 过程。
无论 Phase2 的决议是 commit 还是 rollback,事务性资源的锁都要保持到 Phase2 完成才释放。
设想一个正常运行的业务,大概率是 90% 以上的事务最终应该是成功提交
的,我们是否可以在 Phase1 就将本地事务提交呢?这样 90% 以上的情况下,可以省去 Phase2 持锁的时间,整体提高效率。
上图得知:
- 分支事务中数据的 本地锁 由本地事务管理,在分支事务 Phase1 结束时释放。
- 同时,随着本地事务结束,连接 也得以释放。
- 分支事务中数据的 全局锁 在事务协调器侧管理,在决议 Phase2 全局提交时,全局锁马上可以释放。只有在决议全局回滚的情况下,全局锁 才被持有至分支的 Phase2 结束。
这个设计,极大地减少了分支事务对资源(数据和连接)的锁定时间,给整体并发和吞吐的提升提供了基础。
四、XA 模式 运行在 Seata 定义的事务框架内
介绍xa架构在seata中的体现。确切来说seata at是xa的实现,并且进行了改进。
执行阶段(Execute):
XA start/XA end/XA prepare + SQL + 注册分支
完成阶段(Finish):
XA commit/XA rollback
XA 模式的基本设计目标,两个主要方面:
从 场景 上,满足 全局一致性 的需求。
从 应用上,保持与 AT 模式一致的无侵入。
从 机制 上,适应分布式微服务架构的特点。
整体思路:
与 AT 模式相同的:以应用程序中 本地事务 的粒度,构建到 XA 模式的 分支事务。
通过数据源代理,在应用程序本地事务范围外,在框架层面包装 XA 协议的交互机制,把 XA 编程模型 透明化。
把 XA 的 2PC 拆开,在分支事务执行阶段的末尾就进行 XA prepare,把 XA 协议完美融合到 Seata 的事务框架,减少一轮 RPC(远程过程调用,简单的理解是一个节点请求另一个节点提供的服务) 交互。
(注:XA start 需要 Xid 参数。这个 Xid 需要和 Seata 全局事务的 XID 和 BranchId 关联起来,以便由 TC 驱动 XA 分支的提交或回滚。目前 Seata 的 BranchId 是在分支注册过程,由 TC 统一生成的,所以 XA 模式分支注册的时机需要在 XA start 之前。)
五:Seata AT与XA的优劣
第一点:由于上面我们总结了,其实AT就是一个自实现的XA事务,所以其实可以知道,AT在sql支持上是远不及利用本地事务的XA模式,既然AT需要做sql解析那么背后的实现只能自己来解决,也就是靠Seata社区的贡献者们来贡献解决方案,这是一个长期性的关键问题,但是依然有很多用户选择了重写sql来获取AT事务模式的支持,在sql支持上XA无疑是完胜的;
第二点:隔离性,Seata AT模式通过解析sql获取涉及的主键id,生成行锁,也就是AT模式的隔离就是靠全局锁来保证,粒度细至行级,锁信息存储在Seata-Server一侧。XA模式的隔离性就是由本地数据库保证,锁存储在各个本地数据库中。由于XA模式一旦执行了prepare后,再也无法重入这个XA事务也无法跟其他XA事务共享锁。因为XA协议仅是通过XID来start一个XA事务,本身它不存在所谓的分支事务说法,它本事就是一个XA事务而已,也就是说它只管它自己。
第三点:入侵性,通过我们以上的信息,其实可以发现,谁更底层,谁的入侵性更小,所以由数据库自身所支持的XA模式来说,无疑入侵最小,使用成本最低
上图中,右侧图1是at模式运行时,图2时xa模式运行时。可以很明显,xa的阻塞带来的性能下降时非常厉害的,特别是你的分支事务非常多,每个资源的释放必须等到每个分支的数据库去单独释放,后续的事务才能进入。虽然XA带来的无侵入非常高,但是由于性能下降的程度太大,也就促使了AT的诞生,而现在AT,TCC,SAGA的模式的接受度也越来越高,这也正说明了开发者对性能的要求。AT可以看作时由Seata社区进行全方面优化,自研的XA模式,最大特点就是解决了XA模式的性能差的问题。
按理来说seata at本地锁是行锁,即不同行操作不会冲突。并且tx1分支事务提交后会释放本地锁,虽然可能会回滚,但是只要不回滚,那么另一个全局事务tx2中的分支事务可以读取同一行数据进行操作就不会产生脏数据,只是在提交全局操作时,通过全局锁机制保证避免tx2读到脏数据,但
tx1 tx2 几乎是并行
的,只是tx2提交全局事务会等待一会。
xa中的锁一直持有,是表锁还是行锁?答:也是行锁,详情参见 java jta xa事务