分布式事务简单方案(分布式事务的原理和解决方案)
分布式事务简单方案(分布式事务的原理和解决方案)如果在某个分布式系统中无副本,那么必然满足强一致性,同时也满足可用性,但是如果这个数据宕机了,那么可用性就得不到保证。另外 Eric Brewer教授指出现代 WEB 服务无法同时满足这 3 个属性,说的是无法同时满足,那这是为什么呢?传统的 ACID 模型肯定无法解决分布式环境下的挑战,基于此加州大学伯克利分校 Eric Brewer教授提出 CAP 定理,他指出 现代 WEB 服务无法同时满足以下 3 个属性:关于一致性的理解后面分出来三个方向:关于一致性的理解不同,后面对于 CAP 理论的实现就有所不同。
分布式事务的定义什么是分布式事务?把这个概念拆开来讲有2个小概念,分布式系统 事务。分布式系统它是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。事务是具备原子性、一致性、隔离性和持久性(简称 ACID)4个特性的多个连续操作。
- 原子性(Atomicity),可以理解为一个事务内的所有操作要么都执行,要么都不执行。
- 一致性(Consistency),可以理解为数据是满足完整性约束的,也就是不会存在中间状态的数据,比如你账上有400,我账上有100,你给我打200块,此时你账上的钱应该是200,我账上的钱应该是300,不会存在我账上钱加了,你账上钱没扣的中间状态。
- 隔离性(Isolation),指的是多个事务并发执行的时候不会互相干扰,即一个事务内部的数据对于其他事务来说是隔离的。
- 持久性(Durability),指的是一个事务完成了之后数据就被永远保存下来,之后的其他操作或故障都不会对事务的结果产生影响。
分布式事务就是在分布式系统中支持事务的4个特征,即它在跨节点的多个任务执行过程中保证,要么全部成功,要么全部失败。与单机版的事务不同的是,单机是把多个命令打包成一个统一处理,分布式事务是将多个机器上执行的命令打包成一个命令统一处理。分布式事务最核心的还是其 ACID 特性,只是这种 ACID 变换了场景。
分布式事务要解决的问题数据库拆分:单体系统访问多个数据库实例,由于数据分布在不同的数据库实例,需要通过不同的数据库连接去操作数据,此时就会产生分布式事务。跨DB产生分布式事务。
服务拆分:多服务访问同一个数据库实例。随着模块越来越多,项目越来越庞大,我们会将模块拆分成不同的项目,那么两个服务需要跨网络远程调用,两个服务持有了不同的数据库连接进行数据库操作,此时就会产生分布式事务。跨网络远程调用产生分布式事务。
CAP 定理
传统的 ACID 模型肯定无法解决分布式环境下的挑战,基于此加州大学伯克利分校 Eric Brewer教授提出 CAP 定理,他指出 现代 WEB 服务无法同时满足以下 3 个属性:
- 一致性(Consistency) : 所有的客户端都能返回最新的操作。
- 可用性(Availability) : 非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。
- 分区容错性(Partition tolerance) : 即使出现单个组件无法可用,操作依然可以完成。
关于一致性的理解后面分出来三个方向:
- 强一致:任何一次读都能读到某个数据的最近一次写的数据。系统中的所有进程,看到的操作顺序,都和全局时钟下的顺序一致。简言之,在任意时刻,所有节点中的数据是一样的。
- 弱一致:数据更新后,如果能容忍后续的访问只能访问到部分或者全部访问不到,则是弱一致性。
- 最终一致:不保证在任意时刻任意节点上的同一份数据都是相同的,但是随着时间的迁移,不同节点上的同一份数据总是在向趋同的方向变化。简单说,就是在一段时间后,节点间的数据会最终达到一致状态。
关于一致性的理解不同,后面对于 CAP 理论的实现就有所不同。
另外 Eric Brewer教授指出现代 WEB 服务无法同时满足这 3 个属性,说的是无法同时满足,那这是为什么呢?
如果在某个分布式系统中无副本,那么必然满足强一致性,同时也满足可用性,但是如果这个数据宕机了,那么可用性就得不到保证。
如果一个系统满足 AP,那么一致性又得不到保证。所以 CAP 原则的精髓就是要么 AP,要么 CP,要么 AC,但是不存在 CAP。
BASE 定理
基于前面提到的 CAP,反正我们都无法同时满足CAP,设计系统的最高目的就是让他跑下去不出错,那么是不是可以不要求强一致性,最终让他一致即可。所以后面又提出来了 BASE 定理:
- Basically Available(基本可用)
- Soft state(软状态)
- Eventually consistent(最终一致性)
基于现在大型分布式系统的复杂性,我们无法保证服务永远达到999,那么是否可以取舍,核心服务达到999,非核心服务允许挂为了保全核心服务。另外在非核心服务 down 机过程中允许系统暂时出现不一致但是这个不一致并不影响系统的核心功能使用。
最终系统恢复,所有服务一起修复数据,最终达到一致的状态。
业内通常把严格遵循 ACID 的事务称为刚性事务,而基于 BASE 思想实现的事务称为柔性事务。柔性事务并不是完全放弃了 ACID,仅仅是放宽了一致性要求:事务完成后的一致性严格遵循,事务中的一致性可适当放宽。
常见的分布式事务实现方案分布式事务实现方案从类型上去分刚性事务、柔型事务。刚性事务:通常无业务改造,强一致性,原生支持回滚/隔离性,低并发,适合短事务。柔性事务:有业务改造,最终一致性,实现补偿接口,实现资源锁定接口,高并发,适合长事务。
- 刚性事务:XA 协议(2PC、JTA、JTS)、3PC
- 柔型事务:TCC/FMT、Saga(状态机模式、Aop模式)、本地事务消息、消息事务(半消息)、最多努力通知型事务
4.1 2PC
2PC 即两阶段提交协议,是将整个事务流程分为两个阶段,准备阶段(Prepare phase)、提交阶段(commit phase),2 是指两个阶段,P 是指准备阶段,C 是指提交阶段。
具体流程如下:
准备阶段(Prepare phase): 事务管理器给每个参与者发送Prepare消息,每个数据库参与者在本地执行事 务,并写本地的Undo/Redo日志,此时事务没有提交。(Undo日志是记录修改前的数据,用于数据库回滚,Redo日志是记录修改后的数据,用于提交事务后写入数 据文件)
提交阶段(commit phase): 如果事务管理器收到了参与者的执行失败或者超时消息时,直接给每个参与者 发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据事务管理器的指令执行提交或者回滚操 作,并释放事务处理过程中使用的锁资源。
2PC方案的存在的问题:
- 同步阻塞:当参与事务者存在占用公共资源的情况,其中一个占用了资源,其他事务参与者就只能阻塞等待资源释放,处于阻塞状态。
- 单点故障:一旦事务管理器出现故障,整个系统不可用。
- 数据不一致:在阶段二,如果事务管理器只发送了部分 commit 消息,此时网络发生异常,那么只有部分参与者接收到 commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
- 不确定性:当事务管理器发送 commit 之后,并且此时只有一个参与者收到了 commit,那么当该参与者与事务管理器同时宕机之后,重新选举的事务管理器无法确定该条消息是否提交成功。
总体来说 XA 方案实现简单,但是带来的问题如果放在数据一致性要求严格的场景是无法保证数据正确性的。另外事务管理器单点会带来隐患,同步阻塞模型也致使并发能力弱。
升级的3PC方案旨在解决这些问题,主要有两个改进:
- 增加超时机制。
- 两阶段之间插入准备阶段。
4.2 3PC
3PC 包含了三个阶段,分别是准备阶段、预提交阶段和提交阶段,对应的英文就是:CanCommit、PreCommit 和 DoCommit
3PC 的引入是为了解决提交阶段 2PC 协调者和某参与者都挂了之后新选举的协调者不知道当前应该提交还是回滚的问题。
新协调者来的时候发现有一个参与者处于预提交或者提交阶段,那么表明已经经过了所有参与者的确认了,所以此时执行的就是提交命令。
所以说 3PC 就是通过引入预提交阶段来使得参与者之间的状态得到统一,也就是留了一个阶段让大家同步一下。
但是这也只能让协调者知道该如果做,但不能保证这样做一定对,这其实和上面 2PC 分析一致,因为挂了的参与者到底有没有执行事务无法断定。
所以说 3PC 通过预提交阶段可以减少故障恢复时候的复杂性,但是不能保证数据一致,除非挂了的那个参与者恢复。
让我们总结一下, 3PC 相对于 2PC 做了一定的改进:引入了参与者超时机制,并且增加了预提交阶段使得故障恢复之后协调者的决策复杂度降低,但整体的交互过程更长了,性能有所下降,并且还是会存在数据不一致问题。
所以 2PC 和 3PC 都不能保证数据100%一致,因此一般都需要有定时扫描补偿机制。
我再说下 3PC 我没有找到具体的实现,所以我认为 3PC 只是纯的理论上的东西,而且可以看到相比于 2PC 它是做了一些努力但是效果甚微,所以只做了解即可。
4.3 TCC方案
关于 TCC(Try-Confirm-Cancel)的概念,最早是由 Pat Helland 于 2007 年发表的一篇名为《Life beyond Distributed transactions:an Apostate’s Opinion》的论文提出。 TCC 事务机制相比于上面介绍的 XA,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。解决了其几个缺点:
- 解决了协调者单点,由主业务方发起并完成这个业务活动。业务活动管理器也变成多点,引入集群。
- 同步阻塞:引入超时,超时后进行补偿,并且不会锁定整个资源,将资源转换为业务逻辑形式,粒度变小。
- 数据一致性,有了补偿机制之后,由业务活动管理器控制一致性。
TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。TCC 模型完全交由业务实现,每个子业务都需要实现 Try-Confirm-Cancel 三个接口,对业务侵入大,资源锁定交由业务方。
- Try 阶段:尝试执行,完成所有业务检查(一致性) 预留必须业务资源(准隔离性)。
- Confirm 阶段:确认执行真正执行业务,不作任何业务检查,只使用 Try 阶段预留的业务资源,Confirm 操作满足幂等性。要求具备幂等设计,Confirm 失败后需要进行重试。
- Cancel 阶段:取消执行,释放 Try 阶段预留的业务资源 Cancel 操作满足幂等性 Cancel 阶段的异常和 Confirm 阶段异常处理方案基本上一致。
一个完整的业务活动由一个主业务服务与若干子业务服务组成:
- 主业务服务负责发起并完成整个业务活动;
- 业务服务提供 TCC 型业务操作;
- 业务活动管理器控制业务活动的一致性,它登记业务活动中的操作,并在业务活动提交时确认所有的TCC 型操作的 Confirm 操作,在业务活动取消时调用所有 TCC 型操作的 Cancel 操作。
比如一个转账操作:
- 首先在 Try 阶段先把转账者的钱包冻结起来。
- 在 Confirm 阶段,调用转账接口操作转账,转账成功后解冻。
- 如果 Confirm 阶段成功那么就转账成功,否则执行转账失败确认逻辑。
基于 TCC 实现分布式事务,会将原来只需要一个接口就可以实现的逻辑拆分为 Try、Confirm、Cancel 三个接口,所以代码实现复杂度相对较高,需要在业务中写很多的补偿机制代码。
TCC将事务提交划分成两个阶段,Try即为一阶段,Confirm 和 Cancel 是二阶段并行的两个分支,二选一。从阶段划分上非常像2PC,我们是否可以说TCC是一种2PC或者2PC变种呢?
对比一下 XA 事务模型,TCC 的两阶段提交与 XA 还是有一些区别:
- 2PC 的操作对象在于资源层,对于开发人员无感知;而 TCC 的操作在于业务层,具有较高开发成本。
- 2PC 是一个整体的长事务,也是刚性事务;而 TCC 是一组的本地短事务,是柔性事务。
- 2PC 的 Prepare (表决阶段)进行了操作表决;而 TCC 的 Try 并没有表决准备,直接兼备资源操作与准备能力。
- 2PC 是全局锁定资源,所有参与者阻塞 交互等待 TM 通知;而 TCC 的资源锁定在于 Try 操作,业务方可以灵活选择业务资源的锁定粒度。
TM 事务管理器
TM事务管理器可以实现为独立的服务,也可以让全局事务发起方充当 TM 的角色,TM 独立出来是为了成为公 用组件,是为了考虑系统结构和软件复用。
TM 在发起全局事务时生成全局事务记录,全局事务 ID 贯穿整个分布式事务调用链条,用来记录事务上下文, 追踪和记录状态,由于 Confirm 和 Cancel 失败需进行重试,因此需要实现为幂等,幂等性是指同一个操作无论请求多少次,其结果都相同。
TCC 异常处理
TCC需要注意三种异常处理分别是空回滚、幂等、悬挂
空回滚
在没有调用 TCC 资源 Try 方法的情况下,调用了二阶段的 Cancel 方法,Cancel 方法需要识别出这是一个空回滚,然后直接返回成功。
出现原因是当一个分支事务所在服务宕机或网络异常,分支事务调用记录为失败,这个时候其实是没有执行 Try 阶段,当故障恢复后,分布式事务进行回滚则会调用二阶段的 Cancel 方法,从而形成空回滚。
解决思路是关键就是要识别出这个空回滚。思路很简单就是需要知道一阶段是否执行,如果执行了,那就是正常回滚;如果没执行,那就是空回滚。前面已经说过 TM 在发起全局事务时生成全局事务记录,全局事务 ID 贯穿整个分布式事务调用链条。再额外增加一张分支事务记录表,其中有全局事务 ID 和分支事务 ID,第一阶段 Try 方法里会插入一条记录,表示一阶段执行了。Cancel 接口里读取该记录,如果该记录存在,则正常回滚;如果该记录不存在,则是空回滚。
幂等
通过前面介绍已经了解到,为了保证 TCC 二阶段提交重试机制不会引发数据不一致,要求 TCC 的二阶段 Try、Confirm 和 Cancel 接口保证幂等,这样不会重复使用或者释放资源。如果幂等控制没有做好,很有可能导致数据不一致等严重问题。
解决思路在上述"分支事务记录"中增加执行状态,每次执行前都查询该状态。
悬挂
悬挂就是对于一个分布式事务,其二阶段 Cancel 接口比 Try 接口先执行。
出现原因是在 RPC 调用分支事务 Try 时,先注册分支事务,再执行 RPC 调用,如果此时 RPC 调用的网络发生拥堵,通常 RPC 调用是有超时时间的,RPC 超时以后,TM 就会通知 RM 回滚该分布式事务,可能回滚完成后,RPC 请求才到达参与者真正执行,而一个 Try 方法预留的业务资源,只有该分布式事务才能使用,该分布式事务第一阶段预留的业务资源就再也没有人能够处理了,对于这种情况,我们就称为悬挂,即业务资源预留后没法继续处理。
解决思路是如果二阶段执行完成,那一阶段就不能再继续执行。在执行一阶段事务时判断在该全局事务下,"分支事务记录"表中是否已经有二阶段事务记录,如果有则不执行 Try。
4.4 本地消息表
执行流程:
- 消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过 MQ 发送到消息的消费方。如果消息发送失败,会进行重试发送。
- 消息消费方,需要处理这个消息,并完成自己的业务逻辑。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。
- 此时如果本地事务处理成功,表明已经处理成功了。如果处理失败,那么就会重试执行。
- 生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。
4.5 消息事务
RocketMQ 就很好的支持了消息事务,让我们来看一下如何通过消息实现事务。
第一步先给 Broker 发送事务消息即半消息,半消息不是说一半消息,而是这个消息对消费者来说不可见,然后发送成功后发送方再执行本地事务。
再根据本地事务的结果向 Broker 发送 Commit 或者 RollBack 命令。
并且 RocketMQ 的发送方会提供一个反查事务状态接口,如果一段时间内半消息没有收到任何操作请求,那么 Broker 会通过反查接口得知发送方事务是否执行成功,然后执行 Commit 或者 RollBack 命令。
如果是 Commit 那么订阅方就能收到这条消息,然后再做对应的操作,做完了之后再消费这条消息即可。
如果是 RollBack 那么订阅方收不到这条消息,等于事务就没执行过。
可以看到通过 RocketMQ 还是比较容易实现的,RocketMQ 提供了事务消息的功能,我们只需要定义好事务反查接口即可。
可以看到消息事务实现的也是最终一致性。
eBay 的架构师Dan Pritchett,曾在一篇解释BASE 原理的论文《Base:An Acid Alternative》中提到一个eBay 分布式系统一致性问题的解决方案。它的核心思想是将需要分布式处理的任务通过消息或者日志的方式来异步执行,消息或日志可以存到本地文件、数据库或消息队列,再通过业务规则进行失败重试,它要求各服务的接口是幂等的。
描述的场景为,有用户表user 和交易表transaction,用户表存储用户信息、总销售额和总购买额,交易表存储每一笔交易的流水号、买家信息、卖家信息和交易金额。如果产生了一笔交易,需要在交易表增加记录,同时还要修改用户表的金额。
论文中提出的解决方法是将更新交易表记录和用户表更新消息放在一个本地事务来完成,为了避免重复消费用户表更新消息带来的问题,增加一个操作记录表updates_applied来记录已经完成的交易相关的信息。
这个方案的核心在于第二阶段的重试和幂等执行。失败后重试,这是一种补偿机制,它是能保证系统最终一致的关键流程。
4.6 最大努力通知
最大努力通知的方案实现比较简单,适用于一些最终一致性要求较低的业务。
执行流程:
- 系统 A 本地事务执行完之后,发送个消息到 MQ。
- 这里会有个专门消费 MQ 的服务,该服务会消费 MQ 并调用系统 B 的接口。
- 要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B,反复 N 次,最后还是不行就放弃。
SAGA 事务模型#
Saga是什么?Saga的定义是 “长时间活动的事务 ”(Long Lived Transaction,后文简称为LLT)。他是普林斯顿大学 HECTOR GARCIA-MOLINA 教授在1987年的一篇关于分布式数据库的论文中提出来的概念。
Long Lived 从字面意义上不清晰,Long 到底意味着多长?事务持续时间是一个小时、一天甚至一周吗?其实都不是,时间跨度并不重要。重要的是什么?关键的是跨系统的多次“事务”,Saga 往往由多个外部子事务构成,需要通过多次外部系统的消息交互,才能将整体事务从开始迁移到结束状态,这和我们原来常见的在一个数据库的短事务不一样。比如一个旅行的订单,是由机票、旅馆、租车三个子订单构成,都需要外部的确认,缺任何一个步骤,不能成行,这就是一个典型的 LLT。
看起来 Sage 的定义与别的分布式事务没有什么不同。分布式事务不就是多个不同的子事务构成一个整体吗?再来看看 补偿机制:
每个本地事务有相应的执行模块和补偿模块,当 Sage 事务中的任意一个本地事务出错, 可以通过调用相关事务对应的补偿方法恢复,达到事务的最终一致性。
Saga 模型是把一个分布式事务拆分为多个本地事务,每个本地事务都有相应的执行模块和补偿模块(对应TCC 中的 Confirm 和 Cancel),当 Saga 事务中任意一个本地事务出错时,可以通过调用相关的补偿方法恢复之前的事务,达到事务最终一致性。
由于 Saga 模型中没有 Prepare 阶段,因此事务间不能保证隔离性,当多个 Saga 事务操作同一资源时,就会产生更新丢失、脏数据读取等问题,这时需要在业务层控制并发,例如:
- 在应用层面加锁;
- 应用层面预先冻结资源。
Saga 恢复方式
Saga 支持向前和向后恢复:
- 向后恢复:补偿所有已完成的事务,如果任一子事务失败;
- 向前恢复:重试失败的事务,假设每个子事务最终都会成功。
虽然 Saga 和 TCC 都是补偿事务,但是由于提交阶段不同,所以两者也是有不同的:
- Saga 没有 Try 行为直接 Commit,所以会留下原始事务操作的痕迹,Cancel 属于不完美补偿,需要考虑对业务上的影响。TCC Cancel 是完美补偿的 Rollback,补偿操作会彻底清理之前的原始事务操作,用户是感知不到事务取消之前的状态信息的。
- Saga 的补偿操作通常可以异步执行,TCC 的 Cancel 和 Confirm 可以跟进需要是否异步化。
- Saga 对业务侵入较小,只需要提供一个逆向操作的 Cancel 即可;而 TCC 需要对业务进行全局性的流程改造。
- TCC 最少通信次数为 2n,而 Saga 为 n(n=子事务的数量)。
因为也是采用补偿机制,那么必然要求服务保持幂等性,如果服务调用超时需要通过幂等性来避免多次请求带来的问题。
事务特性的满足:
原子性:Saga 协调器保证整体事务要么全部执行成功,要么全部回滚。
一致性:Sage 保证最终一致性。
持久性:Saga 将整体事务拆分成独立的本地事务,所以持久性在本地事务中很好实现。
但是隔离性 Saga 无法实现,因为大事务被拆分为多个小事务,每个事务提交的时机不同很难保证已提交的小事务不被别人可见。
目前业界提供两类 Saga 的实现方式:
- 一种是集中式协调的实现方式。
- 集中式协调方式就是通过一个 Saga 对象来追踪所有的 Saga 子任务的调用,由它来管理,追踪整个事务是否应该提交或补偿。
- 这种方式带来的缺点就是这种协调方式必然要与第一个Saga 事务耦合,即与业务耦合在一起。
- 一种是分布式实现方式。
- 分布式协调方式肯定就能避免耦合的问题。分布式实现的方案也很多,比如通过事件机制来实现,一条 Saga 事务链上的所有事务都订阅同一个事件,如果失败则通过失败对应的事件消息来回滚即可。
- 这种方式带来的好处肯定是显而易见的,但是也会有另一个问题,多个事件带来的肯定是高并发的处理,那么会不会因为多个事件处理相关的问题带来一些循环依赖的问题。
Seata
Seata(Simple Extensible Autonomous Transaction Architecture,简单可扩展自治事务框架)是 2019 年 1 月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。
Seata 会有 4 种分布式事务解决方案,分别是 AT 模式、TCC 模式、Saga 模式和 XA 模式。
XA 模式
XA 模式是 Seata 将会开源的另一种无侵入的分布式事务解决方案,任何实现了 XA 协议的数据库都可以作为资源参与到分布式事务中,目前主流数据库,例如 MySql、Oracle、DB2、Oceanbase 等均支持 XA 协议。
XA 协议有一系列的指令,分别对应一阶段和二阶段操作。“xa start” 和 “xa end” 用于开启和结束XA 事务;“xa prepare” 用于预提交 XA 事务,对应一阶段准备;“xa commit”和“xa rollback”用于提交、回滚 XA 事务,对应二阶段提交和回滚。
在 XA 模式下,每一个 XA 事务都是一个事务参与者。分布式事务开启之后,首先在一阶段执行“xa start”、“业务 SQL”、“xa end”和 “xa prepare” 完成 XA 事务的执行和预提交;二阶段如果提交的话就执行 “xa commit”,如果是回滚则执行“xa rollback”。这样便能保证所有 XA 事务都提交或者都回滚。
XA 模式下,用户只需关注自己的“业务 SQL”,Seata 框架会自动生成一阶段、二阶段操作;XA 模式的实现如下:
- 一阶段:
在 XA 模式的一阶段,Seata 会拦截“业务 SQL”,在“业务 SQL”之前开启 XA 事务(“xa start”),然后执行“业务 SQL”,结束 XA 事务“xa end”,最后预提交 XA 事务(“xa prepare”),这样便完成 “业务 SQL”的准备操作。
- 二阶段提交:
执行“xa commit”指令,提交 XA 事务,此时“业务 SQL”才算真正的提交至数据库。
- 二阶段回滚:
执行“xa rollback”指令,回滚 XA 事务,完成“业务 SQL”回滚,释放数据库锁资源。
XA 模式下,用户只需关注“业务 SQL”,Seata 会自动生成一阶段、二阶段提交和二阶段回滚操作。XA 模式和 AT 模式一样是一种对业务无侵入性的解决方案;但与 AT 模式不同的是,XA 模式将快照数据和行锁等通过 XA 指令委托给了数据库来完成,这样 XA 模式实现更加轻量化。
AT 模式
AT 模式是一种无侵入的分布式事务解决方案。在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。
AT 模式的一阶段、二阶段提交和回滚
均由 Seata 框架自动生成,用户只需编写“业务 SQL”,便能轻松接入分布式事务,AT 模式是一种对业务无任何侵入的分布式事务解决方案。
TCC 模式
2019 年 3 月份,Seata 开源了 TCC 模式,该模式由蚂蚁金服贡献。TCC 模式需要用户根据自己的业务场景实现 Try、Confirm 和 Cancel 三个操作;事务发起方在一阶段 执行 Try 方式,在二阶段提交执行 Confirm 方法,二阶段回滚执行 Cancel 方法。
TCC 三个方法描述:
- Try:资源的检测和预留;
- Confirm:执行的业务操作提交;要求 Try 成功 Confirm 一定要能成功;
- Cancel:预留资源释放。
用户接入 TCC 模式,最重要的事情就是考虑如何将业务模型拆成 2 阶段,实现成 TCC 的 3 个方法,并且保证 Try 成功 Confirm 一定能成功。相对于 AT 模式,TCC 模式对业务代码有一定的侵入性,但是 TCC 模式无 AT 模式的全局行锁,TCC 性能会比 AT 模式高很多。
Saga 模式
Saga 模式是 Seata 即将开源的长事务解决方案,将由蚂蚁金服主要贡献。在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。
分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式事务提交。如果任何一个正向操作执行失败,那么分布式事务会去退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。
Saga 模式下分布式事务通常是由事件驱动的,各个参与者之间是异步执行的,Saga 模式是一种长事务解决方案。
ServiceCombServiceComb 是华为开源的微服务框架,目前已升级为 Apache 顶级项目。 准确来说它并不是一个纯粹的分布式事务框架而是微服务框架,最开始的版本是 Go 语言 后面支持了 Java。
ServiceComb 由 3 个子项目组成:
- java-chassis:服务治理
- service-center:服务注册
- saga:分布式事务解决
从名字上看很显然是基于 Saga 模式开发的柔性事务方案。Saga系统分为两部分:Alpha 和 Omega。Alpha 是独立的服务,扮演事务协调器的作用。Omega 作为开发组件,和业务进程运行在一起。
Omega 会以切面编程的方式向应用程序注入相关的处理模块。这里有拦截请求的模块, 用来帮助我们构建分布式事务调用的上下文。 同时在事务处理初始阶段处理事务的相关准备的操作,例如创建 Saga 起始事件,以及相关的子起始事件, 根据事务的执行的成功或者失败生产相关的事务终止或者失败事件。
Omega 会与 Alpha 进行链接会把这些事件通知给 Alpha。 Alpha 可以在后台进行分析,根据 Saga 事务执行的情况给 Omega 下达相关的指令进行相关的回滚恢复。
这样设计的好处是 Saga 实现代码与用户的代码分离, 用户只需要添加几个 annotation,Saga 实现就能 Saga 事件的执行情况并进行相关的处理。
总结可以看出 2PC 和 3PC 是一种强一致性事务,不过还是有数据不一致,阻塞等风险,而且只能用在数据库层面。
而 TCC 是一种补偿性事务思想,适用的范围更广,在业务层面实现,因此对业务的侵入性较大,每一个操作都需要实现对应的三个方法。
本地消息、事务消息都是最终一致性事务,因此适用于一些对时间不敏感的业务。