摘抄自:https://www.cnblogs.com/linchenguang/p/13887010.html#/c/subject/p/13887010.html
一、分布式事务产生得原因:
1.1、数据库分库分表
当数据库单表一年产生的数据超过1000W,那么就要考虑分库分表,具体分库分表的原理在此不做解释,以后有空详细说,简单的说就是原来的一个数据库变成了多个数据库。这时候,如果一个操作既访问01库,又访问02库,而且要保证数据的一致性,那么就要用到分布式事务
1.2应用SOA化
所谓的SOA化,就是业务的服务化。比如原来单机支撑了整个电商网站,现在对整个网站进行拆解,分离出了订单中心、用户中心、库存中心。对于订单中心,有专门的数据库存储订单信息,用户中心也有专门的数据库存储用户信息,库存中心也会有专门的数据库存储库存信息。这时候如果要同时对订单和库存进行操作,那么就会涉及到订单数据库和库存数据库,为了保证数据一致性,就需要用到分布式事务。
二、分布式事务解决方案XA模式
XA是一个分布式事务协议,由Tuxedo提出。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。XA实现分布式事务的原理如下:
1. 准备阶段:准备阶段,每个资源管理器都会被轮训一遍,事务管理器给每个资源管理器发送Prepare
消息,每个资源管理器要么直接返回失败(如权限验证失败)或异常,要么在本地执行事务等等,但不Commoit
,处于Ready
状态。
2. 提交阶段:如果事务管理器收到了资源管理器的失败信息(如异常、超时等),直接给每个资源管理器发送回滚(Rollback
)消息;否则,发送提交(Commit
)消息;资源管理器根据事务管理器的指令执行Commit
或者Rollback
操作,释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源)
1、同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
2、单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
3、数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。
4、二阶段无法解决的问题:协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
总的来说,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 参与,释放锁非常快;另外,全局提交的事务完成阶段异步化。
四、XA 模式 运行在 Seata 定义的事务框架
执行阶段(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模式的性能差的问题。