在这篇文章中我们对事务有了基本的了解, 大多数场景下,我们的应用都只需要操作单一的数据库,这种情况下的事务称之为单机事务或者本地事务(Local Transaction)。单机事务的ACID特性是数据库直接提供支持。分布式事务与单机事务一样都是由一组操作序列组成,不同的是单机事务只是在单机上执行,而分布式事务则是在多台机器上执行。单机事务示意图:
当下互联网发展如火如荼,绝大部分公司都进行了数据库拆分和服务化(SOA)。在这种情况下,完成某一个业务功能可能需要横跨多个服务,操作多个数据库。这就涉及到到了分布式事务,用需要操作的资源位于多个资源服务器上,而应用需要保证对于多个资源服务器的数据的操作,要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同资源服务器的数据一致性。分布式事务示意图:
对于分布式数据库系统,事务的一系列操作序列会被拆分为子操作序列,然后在多台机器上执行。
分布式事务同样具有ACID四个特性,但是因为分布式数据库的分布特性,使其又有一些不同。例如,为了保证分布式事务的原子性,必须保证组成该全局事务的所有子事务要么全部提交,要么全部回滚,不允许出现有些场地上的子事务提交,而有些场地上的子事务回滚。因此,在分布式事务执行的过程中,要比单机事务复杂的多,因为分布式事务除了要保证各个子事务的ACID特性外,还需要对这些子事务进行协调,决定各个子事务的提交与回滚,以保证全局事务的ACID特性。另外,在分布式事务中,还涉及大量的网络通信,需要考虑到网络的影响。同时,分布式事务实现方案还必须要考虑性能的问题,如果为了严格保证ACID特性,导致性能严重下降,那么对于一些要求快速响应的业务,是无法接受的。分布式事务需要一套规范的协议应对网络、主机故障等问题,就产生了2套规范 :XA 规范和JTA,并衍生出二阶段提交协议和三阶段提交协议。
通过上文,相信大家应该对分布式事务有了一些概念上的了解,下面我们将介绍分布式事务的规范和提交协议。
X/Open DTP模型与XA 规范
X/Open 组织(即现在的 Open Group,官网地址:http://www.opengroup.org/)定义了分布式事务处理(Distributed Transaction Processing,简称DTP)模型。
DTP 参考模型: <>
DTP XA规范:<< Distributed Transaction Processing: The XA Specification>>
在<>第3版中,规定了构成DTP模型的5个基本元素:
应用程序-AP(Application Program):用于定义事务边界(即定义事务的开始和结束),并且在事务边界内对资源进行操作。
资源管理器-RM(Resource Manager):如数据库、文件系统等,并提供访问资源的方式。
事务管理器 -TM(Transaction Manager):负责分配事务唯一标识,监控事务的执行进度,并负责事务的提交、回滚等中间件。
通信资源管理器-CRM(Communication Resource Manager):控制一个TM域(TM domain)内或者跨TM域的分布式应用之间的通信,这个组件是消息中间件。
通信协议-CP(Communication Protocol):提供CRM提供的分布式应用节点之间的底层通信服务。
在分布式事务处理环境中,多个数据库可能需要共同完成一个工作,这个工作就是一个全局事务。在一个事务中可能更新几个不同的数据库,此时一个数据库对自己内部所做操作的提交不仅需要本身的操作成功,还需要全局事务相关的其他数据库的操作成功。如果任一数据库的任一操作失败,则参与此事务的所有数据库所做的所有操作都必须回滚。XA就是X/Open DTP 定义的事务中间件与数据库之间的接口规范(即接口函数),交易中间件用它来通知数据库事务的开始、结束、提交、回滚等,XA 接口函数由数据库厂商提供,根据这一思想衍生出二阶段提交协议和三阶段提交协议。
分布式事务的提交协议
对于分布式事务来讲,全局事务的正确执行依赖各个子事务的正确执行。只有当各个局部操作都正确执行后,全局事务才可以提交,当发生异常要回滚全局事务时,所有局部操作也应回滚。因此,所有子事务均正确提交是分布式事务提交的前提。为实现分布式事务的提交,普遍采用两阶段提交协议(2 Phase Commit),简称2PC协议。
我们已经知道分布式事务在执行时会被分解为多个场地上的子事务来执行。为协调各个场地上的子事务的执行,我们需要一个协调者,而各个子事务的具体执行,需要本地的参与者来完成。为进一步理解两阶段提交协议,我们先了解下协调者与参与者这两个概念:
-
协调者:协调各个子事务的执行,负责决定所有子事务的提交或回滚;
-
参与者:负责各个子事务的提交与回滚,并向协调者提出子事务的提交或回滚意向。
协调者与参与者的关系如下图所示:
协调者和每个参与者均拥有一个本地日志文件,用来记录各自的执行过程。无论是协调者还是参与者,他们在进行操作前都必须将该操作记录到相应的日志文件中,以用来进行事务故障恢复。
协调者可以向各个参与者发送命令,使各个参与者在协调者的领导下执行命令,各个参与者也可以将自身的命令执行状态以应答的形式反馈给协调者,由协调者收集并分析这些应答以决定下一步的操作。
两阶段提交协议的基本思想
两阶段提交协议是为了实现分布式事务提交而采用的协议。其基本思想是把全局事务的提交分为如下两个阶段:
1.决定阶段:由协调者向各个参与者发出“预提交”(Prepare)命令,然后等待应答,若所有的参与者返回“准备提交”(Ready)应答,则该事务满足提交条件。如果至少有一个子事务返回“准备废弃”(Abort)应答,则该事务不能提交;
2.执行阶段:在事务具备提交条件的情况下,协调者向各个参与者发出“提交”(Commit)命令,各个参与者执行提交;否则,协调者向各个参与者发出“废弃”(Abort)命令,各个参与者执行回滚,放弃对数据库的修改。无论是“提交”还是“废弃”,各参与者执行完毕后都需要向协调者返回“确认”(ACK)应答,通知协调者事务执行结束。
两阶段提交协议的基本思想可以用下图表示:
两阶段提交协议的基本流程如下图所示:
准备阶段指事务协调者(事务管理器TM)向每个参与者(资源管理器RM)发送准备消息,每个参与者要么直接返回失败消息(如权限验证失败),要么在本地执行事务,写本地的 redo 和undo日志但不提交,可以进一步将准备阶段分为以下三步。
(1)协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应。
(2)参与者节点执行询问发起为止的所有事务操作,并将 undo 信息和 redo 信息写入日志。
(3)各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个“同意”消息;如果参与者节点的事务操作实际执行失败,则它返回一个“中止”消息。
提交阶段指如果协调者收到了参与者的失败消息或者超时,则直接向每个参与者发送回滚(Rollback)消息,否则发送提交(Commit)消息,参与者根据协调者的指令执行提交或者回滚操作,释放所有事务在处理过程中使用的锁资源。
二阶段提交所存在的缺点如下。
(1)同步阻塞问题,在执行过程中所有参与节点都是事务阻塞型的,当参与者占用公共资源时,其他第三方节点访问公共资源时不得不处于阻塞状态。
(2)单点故障,由于协调者的重要性,一旦协调者发生故障,则参与者会一直阻塞下去。
(3)数据不一致,在二阶段提交的第 2 个阶段中,当协调者向参与者发送 commit 请求之后发生了局部网络异常或者在发送 commit 请求的过程中协调者发生了故障,则会导致只有一部分参与者接收到了 commit 请求,而在这部分参与者在接收到 commit 请求之后就会执行commit操作,其他部分未接收到 commit 请求的机器则无法执行事务提交,于是整个分布式系统便出现了数据不一致的现象。
(4)容错性差:在二阶段提交的提交询问阶段中,参与者出现故障,导致协调者始终无法获取到所有参与者的确认信息,这时协调者只能依靠其自身的超时机制,判断是否需要中断事务。
由于二阶段提交存在诸如同步阻塞、单点问题、数据不一致、宕机等缺陷和缺点,所以,研究者们在二阶段提交的基础上做了改进,提出了三阶段(3PC)提交。
三阶段提交
三阶段提交(Three-phase commit,3PC),也叫作三阶段提交协议(Three-phase commitprotocol),是二阶段提交(2PC)的改进版本。三阶段提交把二阶段提交的准备阶段再次一分为二,这样三阶段提交就有 CanCommit、PreCommit、DoCommit 三个阶段。 与两阶段提交不同的是,三阶段提交有两个改动点:
1、引入超时机制。同时在协调者和参与者中都引入超时机制。
2、在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。
三个阶段为:
(1)CanCommit 阶段:三阶段提交的 CanCommit 阶段其实和二阶段提交的准备阶段很像,协调者向参与者发送 commit 请求,参与者如果可以提交就返回 Yes 响应,否则返回 No 响应。
(2)PreCommit 阶段:协调者根据参与者的反应情况来决定是否可以记录事务的 PreCommit操作。根据响应情况,有以下两种可能。
- 假如协调者从所有参与者那里获得的反馈都是 Yes 响应,则执行事务。
- 假如有任何一个参与者向协调者发送了 No 响应,或者等待超时之后协调者都没有接到参与者的响应,则执行事务的中断。
(3)DoCommit阶段:该阶段进行真正的事务提交,也可以分为执行提交、中断事务两种执行情况。
执行提交的过程如下。
- 协调者接收到参与者发送的ACK响应后,将从预提交状态进入提交状态,并向所有参与者发送doCommit请求。
- 事务提交参与者接收到doCommit请求之后,执行正式的事务提交,并在完成事务提交之后释放所有的事务资源。
- 事务提交完之后,向协调者发送ACK响应。
- 协调者接收到所有参与者的ACK响应之后,完成事务。中断事务的过程如下。
- 协调者向所有参与者发送abort请求。
- 参与者接收到 abort 请求之后,利用其在第 2 个阶段记录的 undo 信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。
- 参与者完成事务回滚之后,向协调者发送 ACK 消息。
- 协调者接收到参与者反馈的 ACK 消息之后,执行事务的中断。
三阶段提交协议在一定程度上解决了两阶段提交协议中的阻塞问题,这是因为当协调者故障或是网络故障时,参与者长时间未收到协调者的命令,参与者可以通过启动恢复处理过程,不必进入等待状态而可以独立的做出决定。
三阶段提交协议虽然可以在一定程度上解决两阶段提交协议中的阻塞问题,但因为流程逻辑复杂,代码编写难度太高,也很难用于实际应用。
同时,为了保证分布式数据库的正确性,除了需要分布式事务的提交协议,我们还需要分布式事务的恢复策略,在发生故障的时候对事务进行恢复,在分布式数据库系统中,除了会出现单机数据库中的故障外,还会出现网络故障、参与者故障这些分布式数据库系统中特有的故障,针对每种故障发生的情况,都需要有特定的恢复策略,可见分布式事务的恢复更是一个复杂的问题,这就涉及到事务日志持久化机制。
小结:2PC与3PC的区别
相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。
了解了2PC和3PC之后,我们可以发现,无论是二阶段提交还是三阶段提交都无法彻底解决分布式的一致性问题。Google Chubby的作者Mike Burrows说过, there is only one consensus protocol, and that’s Paxos” – all other approaches are just broken versions of Paxos. 意即世上只有一种一致性算法,那就是Paxos,所有其他一致性算法都是Paxos算法的不完整版。后面的文章会介绍这个公认为难于理解但是行之有效的Paxos算法。
CAP 理论和BASE理论
2000年7月,加州大学伯克利分校的Eric Brewer教授在ACM PODC会议上提出CAP猜想。Brewer认为在设计一个大规模的分布式系统时会遇到三个特性:一致性(consistency)、可用性(Availability)、分区容错(partition-tolerance),而一个分布式系统最多只能满足其中的2项。2年后,麻省理工学院的Seth Gilbert和Nancy Lynch从理论上证明了CAP。之后,CAP理论正式成为分布式计算领域的公认定理。 通俗意义上来讲,分布式事务就是夸数据库的事务支持。分布式事务最大的挑战在于CAP,其来源在于网络分割P的存在,用户不得不在一致性C和可用性A之间权衡。
1. 一致性
一致性指“all nodes see the same data at the same time”,即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致,不能存在中间状态。例如对于电商系统用户下单操作,库存减少、用户资金账户扣减、积分增加等操作必须在用户下单操作完成后必须是一致的。不能出现类似于库存已经减少,而用户资金账户尚未扣减,积分也未增加的情况。如果出现了这种情况,那么就认为是不一致的。
关于一致性,如果的确能像上面描述的那样时刻保证客户端看到的数据都是一致的,那么称之为强一致性。如果允许存在中间状态,只要求经过一段时间后,数据最终是一致的,则称之为最终一致性。此外,如果允许存在部分数据不一致,那么就称之为弱一致性。
2. 可用性
可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。“有限的时间内”是指,对于用户的一个操作请求,系统必须能够在指定的时间内返回对应的处理结果,如果超过了这个时间范围,那么系统就被认为是不可用的。试想,如果一个下单操作,为了保证分布式事务的一致性,需要10分钟才能处理完,那么用户显然是无法忍受的。“返回结果”是可用性的另一个非常重要的指标,它要求系统在完成对用户请求的处理后,返回一个正常的响应结果,不论这个结果是成功还是失败。
3. 分区容错性
分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
小结: 既然一个分布式系统无法同时满足一致性、可用性、分区容错性三个特点,我们就需要抛弃一个,需要明确的一点是,对于一个分布式系统而言,分区容错性是一个最基本的要求。因为既然是一个分布式系统,那么分布式系统中的组件必然需要被部署到不同的节点,否则也就无所谓分布式系统了。而对于分布式系统而言,网络问题又是一个必定会出现的异常情况,因此分区容错性也就成为了一个分布式系统必然需要面对和解决的问题。因此系统架构师往往需要把精力花在如何根据业务特点在C(一致性)和A(可用性)之间寻求平衡。而前面我们提到的X/Open XA 两阶段提交协议的分布式事务方案,强调的就是一致性;由于可用性较低,实际应用的并不多。而基于BASE理论的柔性事务,强调的是可用性,目前大行其道,大部分互联网公司采可能会优先采用这种方案。
6.2 BASE理论
eBay的架构师Dan Pritchett源于对大规模分布式系统的实践总结,在ACM上发表文章提出BASE理论。文章链接:https://queue.acm.org/detail.cfm?id=1394128
BASE理论是对CAP理论的延伸,核心思想是即使无法做到强一致性(Strong Consistency,CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)。
BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的缩写。
1. 基本可用-BA(Basically Available)
指分布式系统在出现不可预知故障的时候,允许损失部分可用性。
2. 软状态-S( Soft State)
指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性。
3. 最终一致-E( Eventual Consistency)
强调的是所有的数据更新操作,在经过一段时间的同步之后,最终都能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
BASE理论面向的是大型高可用可扩展的分布式系统,和传统的事物ACID特性是相反的。它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。但同时,在实际的分布式场景中,不同业务单元和组件对数据一致性的要求是不同的,因此在具体的分布式系统架构设计过程中,ACID特性和BASE理论往往又会结合在一起。
典型的柔性事务方案
- 最大努力通知(非可靠消息、定期校对)
- 可靠消息最终一致性(异步确保型)
- TCC 协议(两阶段型、补偿型)
TCC(Try-Confirm-Cancel)分布式事务模型相对于 XA 等传统模型,也是一个分为两个阶段的事务提交协议,其特征在于它不依赖资源管理器(RM)对分布式事务的支持,而是通过对业务逻辑的分解来实现分布式事务。
tcc事务提交协议分为两个阶段:
1)阶段一,主业务尝试(try)调用从业务,从业务并不直接执行,而是进行一个预留操作。比如,扣减库存问题,并不直接扣减库存,而是预留一个扣减字段,表示要扣减多少库存。
2)阶段二,在从业务都返回yes的情况下,主业务将会确认(confirm)之前的预留操作,也就是会根据之前的扣减字段直接扣减库存了。那如果不是全部yes的情况下,就会调用取消(cancel)请求,取消之前的扣减字段。
相对于二阶段的XA协议,TCC主要在单点故障和同步阻塞上做了改进,并且在无法收到来自协调者的信息时,会默认执行commit,而不会一直持有事务资源。但其缺点有:
对应用的侵入性强。业务逻辑的每个分支都需要实现try、confirm、cancel三个操作,应用侵入性较强,改造成本高。
实现难度较大。需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。为了满足一致性的要求,confirm和cancel接口必须实现幂等。
tcc与2pc有什么区别
相同点:TCC和2pc都是两个阶段,一阶段并不真正的执行业务,二阶段根据一阶段的结果进行确认或者取消。所以,你会觉得在外观上两者似乎非常相似。
不同点:(1)开发者感知,tcc和2pc的本质区别就是tcc面向的是业务层面,2pc面向的是资源层面,什么意思?我们在学习2pc的时候,我们总说一阶段是prepare事务,也就是不真正的提交事务。也就是说对资源的更新操作实际上并没有执行,记录在事务日志中准备二阶段的commit或者rollback。但是这些对于开发者来说其实是无感知的,开发者仍旧对资源进行单一的更新操作。而tcc,它的一阶段进行try预留操作,也就是说开发者需要从业务层面来考虑这个问题,提供try-confirm-cancel这样的一个业务逻辑来为维护事务提交,开发者对此是感知得很明显的。我们也可以理解为对业务的入侵。
(2)强一致性和最终一致性,2pc在一定程度上我们称之为强一致性,所以2pc的执行过程会独占资源,持有资源的互斥锁,这也是2pc效率比较低的原因。而tcc虽然增加了业务代码的复杂性,但是在业务层面上避免长时间占用资源,通过一种confirm或cancel的补偿机制来完成整个业务操作,也就是保持最终一致性。最终一致性并不需要长时间持有资源的锁,每一个事务其实都是相互独立的,所以tcc的效率会更高。
相关连接:
淘宝大规模SOA系统分布式事务处理PDF:https://download.csdn.net/download/zrg523/12256543
阿里seata分布式事务框架:https://www.cnblogs.com/lay2017/p/12078232.html
seata的官方文档:http://seata.io/zh-cn/docs/overview/what-is-seata.html
seata的github地址:https://github.com/seata/seata
https://www.jianshu.com/p/ad924d2ed8e8