简介
本文介绍分布式事务的一些理论,包括:分布式事务协议、2PC/3PC、TCC、XA。
分布式事务协议
提起分布式事务,最早指涉及的是多个资源的数据库事务问题。不过事务一词含义随着 SOA 架构逐渐扩大,根据上下文不同,可分为两类:
- System transaction; //多指数据库事务
- Business transaction。//多对应一个业务交易
与此同时,分布式事务的含义也在泛化,尤其 SOA、微服务概念流行起来后,多指的是一个业务场景,需要编排很多独立部署的服务时,如何保证交易整体的原子性与一致性问题。
2PC/3PC
TCC
XA
简介
- XA 协议本质上是两阶段提交。
- XA也就是:X/Open DTP(X/Open Distributed Transaction Processing Reference Model) 。
- XA是X/Open 这个组织定义的一套分布式事务的标准(规范和API接口),由各个厂商进行具体的实现。 X/Open DTP 定义了三个组件: AP,TM,RM。
项 | AP | RM | TM |
全称 | 应用程序 Application Program | 资源管理器 Resource Manager | 事务管理器 Transaction Manager |
简介 | 必须实现XA定义的接口,有两种实现:数据库、MQ。 | 提供给AP应用程序编程接口、管理资源管理器。 | |
作用 | 应用程序通过资源管理器对资源进行控制。 | 负责各个本地资源的提交和回滚 |
XA的ACID特性
- 原子性:XA议使用2PC原子提交协议来保证分布式事务原子性
- 隔离性:XA要求每个RMs实现本地的事务隔离,子事务的隔离来保证整个事务的隔离。
- 一致性:通过原子性、隔离性以及自身一致性的实现来保证“数据库从一个一致状态转变为另一个一致状态”;通过MVCC来保证中间状态不能被观察到。
使用场景
使用XA的场景
在同一个事务上下文中需要协调多种资源(多个数据库、多个MQ、数据库+MQ)时,才有必要使用X/Open XA接口。
如果没有XA,送往队列或主题的消息甚至会在事务终止前到达并被读取。而在XA环境下,队列中的消息在事务提交之前不会被释放。此外,如果是协调一个操作型数据库和一个只读数据库(即参考数据库),就不需要XA。
不用XA的场景
场景1:
场景2:互联网中很少使用
原因我觉得有以下几个:
- 性能(阻塞性协议,增加响应时间、锁时间、死锁);
- 数据库支持完善度(MySQL5.7之前都有缺陷);
- 协调者依赖独立的J2EE中间件(早期重量级Weblogic、Jboss、后期轻量级Atomikos、Narayana和Bitronix);
- 运维复杂,DBA缺少这方面经验;
- 并不是所有资源都支持XA协议;
- 大厂懂所以不使用,小公司不懂所以不敢用。
其实也并非不用,例如在 IBM 大型机上基于 CICS 很多跨资源是基于 XA 协议实现的分布式事务,事实上 XA 也算分布式事务处理的规范了。
Saga
解决方案
简介
与XA相比,Saga和TCC本质上都是将两阶段提交从资源层提升到了应用层。
问:如果数据库只能保证本地 ACID 时,那么其中出现交易异常后,如何实现整个交易原子性(A),从而保证一致性(C) 呢?另外在处理过程中如何保证隔离性呢?
答:最直接的方法就是按照逻辑依次调用服务,但出现异常怎么办?那就对那些已经成功的进行补偿,补偿成功就一致了,这种朴素的模型就是 Saga。但 Saga 这种方式并不能保证隔离性,于是出现了 TCC。
当然还有像 Ebay 提出的基于消息表,即可靠消息最终一致模型,但本质上这也属于 Saga 模式的一种特定实现,它的关键点有两个:
- 基于应用共享事务记录执行轨迹;
- 然后通过异步重试确保交易最终一致(这也使得这种方式不适用那些业务上允许补偿回滚的场景)。
这类分布式事务场景并不是微服务才出现的,在SOA时代其实就有了,常见的Saga、TCC、可靠消息最终一致等模型也都是很多年前就有了,只是最近几年随着微服务兴起,这些方案又重新被人关注了起来。
详述
Saga是30年前一篇数据库论里提到的一个概念。其核心思想是将长事务拆分为多个本地短事务,由Saga事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。 Saga的组成:
每个Saga由一系列sub-transaction Ti 组成 每个Ti 都有对应的补偿动作Ci,补偿动作用于撤销Ti造成的结果,这里的每个T,都是一个本地事务。 可以看到,和TCC相比,Saga没有“预留 try”动作,它的Ti就是直接提交到库。
Saga的执行顺序有两种:
- T1, T2, T3, …, Tn
- T1, T2, …, Tj, Cj,…, C2, C1,其中0 < j < n
Saga定义了两种恢复策略:
- 向后恢复,即上面提到的第二种执行顺序,其中j是发生错误的sub-transaction,这种做法的效果是撤销掉之前所有成功的sub-transation,使得整个Saga的执行结果撤销。
- 向前恢复,适用于必须要成功的场景,执行顺序是类似于这样的:T1, T2, …, Tj(失败), Tj(重试),…, Tn,其中j是发生错误的sub-transaction。该情况下不需要Ci。
这里要注意的是,在saga模式中不能保证隔离性,因为没有锁住资源,其他事务依然可以覆盖或者影响当前事务。
还是拿100元买一瓶水的例子来说,这里定义
T1=扣100元,T2=给用户加一瓶水,T3=减库存一瓶水
C1=加100元,C2=给用户减一瓶水,C3=给库存加一瓶水
我们一次进行T1,T2,T3如果发生问题,就执行发生问题的C操作的反向。 上面说到的隔离性的问题会出现在,如果执行到T3这个时候需要执行回滚,但是这个用户已经把水喝了(另外一个事务),回滚的时候就会发现,无法给用户减一瓶水了。这就是事务之间没有隔离性的问题。
可以看见saga模式没有隔离性的影响还是较大,可以参照华为的解决方案:从业务层面入手加入一 Session 以及锁的机制来保证能够串行化操作资源。也可以在业务层面通过预先冻结资金的方式隔离这部分资源, 最后在业务操作的过程中可以通过及时读取当前状态的方式获取到最新的更新。
DTP
简介
DTP(DistributedTransactionProcessing)模型。
DTP几个概念
- 事务:一个事务是一个完整的工作单元,由多个独立的计算任务组成,这多个任务在逻辑上是原子的
- 全局事务:对于一次性操作多个资源管理器的事务,就是全局事务
- 分支事务:在全局事务中,某一个资源管理器有自己独立的任务,这些任务的集合作为这个资源管理器的分支任务
- 控制线程:用来表示一个工作线程,主要是关联AP,TM,RM三者的一个线程,也就是事务上下文环境。简单的说,就是需要标识一个全局事务以及分支事务的关系
大致流程
如果一个事务管理器管理着多个资源管理器,DTP是通过两阶段提交协议来控制全局事务和分支事务。
- 第一阶段:准备阶段 事务管理器通知资源管理器准备分支事务,资源管理器告之事务管理器准备结果
- 第二阶段:提交阶段 事务管理器通知资源管理器提交分支事务,资源管理器告之事务管理器结果
请先
!