分布式事务CAP、BASE、XA、2PC、3PC、TCC、SAGA、本地事务表、事务消息、最大努力通知方案
- ============================== 强一致性方案(XA协议) =========================
- ============================== 最终一致性方案(TCC&Hmily) ====================
- ============================== 最终一致性方案(SAGA) ========================
- ================ 最终一致性方案(本地事务状态表&MQ事务消息&最大努力通知方案) ===========
- ============================== 各方案常见使用场景总结 ========================
1. 什么是CAP理论?
CAP理论是针对分布式数据库而言的,它是指一个分布式系统中,一致性、可用性、分区容错性三者不可兼得。
- 一致性是指“all nodes see the same data at the same time”,即更新操作成功后,所有节点在同一时间的数据完全一致。一致性可以分为客户端和服务端两个不同角度:从客户端角度来看,一致性主要指多个用户并发访问时更新的数据如何被其他用户获取的问题;从服务端来看,一致性则是用户进行数据更新时如何将数复制到整个系统,以保证数据的一致。一致性时在并发读写时才会出现的问题,因此在理解一致性问题时,一定要注意结合考虑并发读写的场景。一主席那个还可以分为强一致性(必须一致)、弱一致性(可以不一致)和最终一致性(一致性允许有延迟)。
- 可用性是指“reads and writes always succeed”,即用户访问数据时,系统是否能正常响应时间返回结果。好的可用性主要是指系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。在通常情况下,可用性与分布式数据冗余、负载均衡等有着很大的关联。
- 分区容错性是指“the system continues to operate despite arbitrary message loss or failure part of the system”,即分布式系统在遇到某个节点或者网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。分区容错性和扩展性紧密相关。在分布式应用中,可能因为一些分布式的原因导致系统无法正常运转。分区容错性是指在部分节点故障或者出现丢包的情况下,集群系统仍然能提供服务,完成数据的访问。分区容错可视为系统中采用多副本策略。
CAP理论认为分布式系统只能兼顾其中的两个特性,即出现CA、CP、AP三种情况,在实践中,可根据实际情况进行权衡,或者在软件层面提供配置方式,由用户决定如何选择CAP策略。CAP理论可用在不同的层面,可以根据CAP原理定制局部的设计策略,例如,在分布式系统中,每个节点自身的数据能保证CA的,但在整体上又要兼顾AP或CP。
2. 什么是BASE理论?
BASE理论是由eBay架构师提出的,是对CAP中一致性和可用性权衡的结果,其中源于对大规模互联网分布式系统的实践的总结,是基于CAP理论演化而来。其核心思想是即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来是系统达到最终一致性。BASE理论是基本可用(Basically Available)、软状态(Soft State)、最终一致性(Eventually Consistent)三个短语的缩写。
- 基本可用(Basically Available)是指分布式系统在出现故障的时候,允许损失部分可用性(例如响应时间、功能上的可用性),允许算是部分可用性。需要注意的是,基本可用绝不等价于系统不可用。响应时间上的损失:正常情况下搜索引擎需要在0.5s内返回给用户相应的查询结果,但是由于出现故障(比如系统部分机房发生断电或断网故障),查询结果的响应时间增加到了1s~2s;功能上的损失:购物网站在购物高峰(如双十一)时,为了保证系统的稳定性,部分消费者可能会被引导一个降级页面。
- 软状态(Soft State)是指允许系统存在中间状态,而该中间状态不影响系统整体可用性。分布式存储一般一份数据会有多个副本,允许不同副本同步的延时就是软状态的体现。Mysql replication的异步复制也是一种体现。
- 最终一致性(Eventually Consistent)是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况。基于BASE理论涌现出的一些关于一致性算法和协议包括:两阶段提交、三阶段提交、paxos算法、raft算法、zab协议。
============================== 强一致性方案(XA协议) =========================
3. XA分布式强一致性事务解决方案(2PC)
XA规范是X/Open组织定义的分布式处理标准,典型的二段式事务解决方案,XA方案作为业界的一种分布式事务的标准,定义了事务管理器和资源管理器之间的协议,用于实现分布式环境下的事务一致性,主流的关系型数据库,Mysql、Oracle、Pgsql等对他都有良好的支持。以下是使用XA规范实现分布式事务的基本原理:
- 客户端连接到主事务管理器并开始一个分布式事务。主事务管理器通过记录事务日志来维护事务的状态。
- 主事务管理器将分支事务请求发送给各个资源管理器。
- 各个资源管理器接收到分支事务请求后,开始执行相应操作,并将事务执行结果记录到本地日志(Undo日志、Redo日志)。
- 当所有的分支事务执行成功,并且资源管理器都能保证事务的原子性和一致性时,各个资源管理器向主事务管理器发送“prepare”消息。
- 主事务管理器接收到所有“prapare”消息后,将所有分支事务进行提交(commit)操作(执行Redo日志进行Commit,执行Undo日志进行RollBack)。
- 当所有的分支事务提交成功后,主事务管理器向客户端发送“commit”消息,客户端收到消息后,事务完成。
如果在上述过程中有任何分支事务执行失败或资源管理器无法保证事务的原子性和一致性,主事务管理器会向各个资源管理器发送“rollback”消息,将所有分支事务回滚。
通过使用XA规范,MySQL可以实现分布式事务的强一致性,主要步骤包括:开始事务、分支事务的执行、分时事务的准备(prepare)、分支事务的提交(commit)以及回滚(rollback)。
使用XA规范的好处是,即使在分布式环境下,所有的分支事务都能保证事务的一致性和原子性。另外,XA还提供了事务的恢复机制,以及处理由于故障或错误导致分布式事务中断的情况。
需要注意的是,使用XA规范需要对数据库进行相应的配置和支持,并且在应用程序中使用事务管理器提供的API来控制事务的开始、提交和回滚操作。
4. XA分布式强一致性事务解决方案(3PC)
2PC协议存在最明显的问题就是同步阻塞,在整个提交过程中,所有资源管理器的逻辑都处于阻塞状态,每个资源管理器都在等待主事务管理器的响应时,无法进行其他操作,极大的限制了分布式的性能;另外主事务管理器在整个提交过程中都很重要,如果主事务管理器出了问题,那么整个过程都会失败,更严重的是所有的资源管理器一直处于资源锁定的状态,从而无法完成事务操作;还有可能发生数据不一致,假设当主事务管理器向所有资源管理器发送commit之后,发生网络抖动主事务管理器在未发送完所有Commit之前自身挂掉了,导致部分资源管理器接收到commit请求,或者是Prepare阶段成功,Commit阶段失败,这都会导致数据不一致。最后是2PC没有设计比较完善的容错机制。
为了解决上述问题,提出了3PC,三段提交协议,该协议是2PC的改进版,与2PC不同是,3PC提交是“非阻塞”协议。3PC在2PC的第一阶段与第二阶段之间插入一个准备阶段,使得原先在两阶段提交中,资源管理器在投票之后,由于主事务管理器发生崩溃或错误,导致资源管理器处无法知晓是否提交或者回滚的“不确定状态”所产生的可能相当长的延时问题得以解决。以下是3PC基本原理:
- CanCommit:事务询问,主事务管理器向所有的资源管理器发送一个包含事务内容的请求,询问是否可以执行事务提交操作,并开始等待各资源管理器的响应; 资源管理器在接收到来自主事务管理器包含了事务内容的请求后,正常情况下,如果自身任务可以顺利执行事务,则反馈Yes响应,并进入准备状态,否则反馈No响应。
- PreCommit:主事务管理器在得到所有资源管理器的响应之后,会根据结果有2中执行操作的情况:执行事务与提交,或中断事务。假如所有资源管理器反馈的都是Yes,那么会执行事务与提交;若任一参与者反馈了No响应,或者在等待超时后,主事务管理器尚无法接收到所有资源管理器反馈,则中断事务。
2.1 如果是执行事务请求,需要做三步:
发送预提交请求:主事务管理器向所有资源管理器发送preCommit请求,并进入prepared阶段;
事务预提交:资源管理器接收到preCommit请求后,会执行事务操作,并将Undo和Redo信息记录到事务日志中;
各资源管理器向主事务管理器反馈执行的结果:若资源管理器成功执行了事务操作,那么反馈Ack。
2.2 如果是回滚请求,需要做两步:
发送中断请求:主事务管理器向所有资源管理器发发出abort请求;
中断事务:无论收到来自主事务管理器的abort请求或者等待主事务管理器请求过程中超时,资源管理器都会中断事务。- DoCommit:该阶段做真正的事务提交或者完成事务回滚,资源管理器会根据接收主事务管理器的请求,进而判断需要事务的提交还是回滚,如果出现网络抖动或其他问题,最终导致资源管理器无法接收到主事务管理器发来的请求,针对这种情况,资源管理器会在等待超时之后,默认认为其他资源管理器接收是成功的,所以会进行事务提交。
============================== 最终一致性方案(TCC&Hmily) ====================
5. 分布式最终一致性事务解决方案(TCC)
TCC是Try、Confirm、Cancel三个词语的缩写,TCC要求每个分支事务实现三个操作:预处理Try、确认Confirm、撤销Cancel。Try操作业务检查及资源预留,Confirm做业务确认操作,Cancel实现一个与Try相反的操作即回滚操作。事务管理器首先发起所有的分支事务的try操作,任何一个分支事务的try操作执行失败,事务管理器会发起所有分支事务的Cancel操作;若try操作全部成功,事务管理器将会发起所有分支事务的Confirm操作,其中Confirm/Cancel操作若执行失败,事务管理器会进行重试。其实现原理如下:
- Try阶段是做业务检查(一致性)及资源预留(隔离性),尝试执行业务逻辑,此阶段仅是一个初步操作,它和后续的Confirm一起才能真正构成一个完整业务逻辑。
- Confirm阶段是确认业务逻辑执行成功并提交事务,Try阶段所有分支事务执行成功后开始执行Confirm。通常情况下,采用TCC则认为Confirm阶段是不会出错。即:只要Try成功,Confirm一定成功。若Confirm阶段真的出错了,需引入重试机制或人工处理。
- Cancel阶段是在业务执行错误需要回滚的状态下执行分支事务的业务取消,预留资源释放。通常情况下,采用TCC则任务Cancel阶段也是一定成功的。若Cancel阶段真的出错,需引入重试机制或人工处理。
事务管理器在发起全局事务时生成全局事务记录,全局事务ID贯穿整个分布式事务调用链条,用来记录事务上下文,追踪和记录状态,由于Confirm和Cancel失败需进行重试,因此需要实现为幂等,幂等性时指同一个操作无论请求多少次,其结果都相同。
6. TCC需要注意三种异常处理
- 空回滚:在没有调用TCC资源Try方法的请款下,调用了二阶段的Cancel方法,Cancel方法需要识别出这是一个空回滚,然后直接返回成功。出现原因是当一个分支事务所在服务宕机或网络异常,分支事务调用记录为失败,这个时候其实是没有执行Try,当故障恢复后,分布式事务进行回滚则会调用二阶段Cancel方法,从而形成空回滚。解决思路的关键是要识别出这个空回滚,思路很简单就是需要知道一阶段是否执行了,如果执行了,那就是正常回滚;如果没有执行,那就是空回滚。前面已经说过事务管理器在发起全局事务时生成全局事务记录,全局事务ID贯穿整个分布式事务调用链条。在而外增加一张分支事务记录表,其中有全局事务ID和分支事务ID,第一阶段Try方法会插入一条记录,表示一阶段执行了,Cancel接口里读取该记录,如果记录存在,则正常回滚;如果该记录不存在,则是空回滚。
- 幂等:通过前面介绍已经了解到,为保证TCC二阶段提交重试机制不会引发数据不一致,要求TCC的二阶段Try、Confirm和Cancel接口保证幂等,这样不会重复使用或者释放资源。如果幂等控制没有做好,很有可能导致数据不一致等严重问题。解决思路在上述“分支事务记录”中增加执行状态,每次执行前都检查该状态。
- 悬挂:就是对于一个分布式事务,其二阶段Cancel接口比Try接口执行。出现原因是在RPC调用分支事务try时,先注册分支事务,在执行RPC调用,如果此时RPC调用发生拥堵,通常RPC调用是有超时时间的,RPC超时以后,事务管理器会通知资源管理器回滚该事务,可能回滚完成后,RPC请求才真正到达,而一个try方法预留的业务资源,只有该分布式事务才能使用,该分布式事务第一阶段预留的业务资源就再没人处理了,对于这种情况称之为悬挂,即业务资源预留后没法继续处理。解决思路时如果二阶段执行完成,那么一阶段就不能再继续执行,在执行一阶段时判断该全局事务下,分支事务记录表中是否已经有二阶段事务记录,如果有则不执行Try。
============================== 最终一致性方案(SAGA) ========================
7. 分布式最终一致性事务解决方案(SAGA)
Saga事务核心思想是将长事务拆分成多个本地短事务并一次正常提交。如果所有短事务均执行成功,那么分布式事务提交;如果出现某个参与者执行本地失败,则有Saga事务协调器根据相反顺序调用补偿操作,回滚已提交的参与者,使分布式事务回到最初的状态。Saga事务基于协议如下:
- 每个Saga事务由一系列幂等有序事务Ti组成。
- 每个Ti都有对应的幂等补偿动作Ci,补偿动作等于撤销Ti造成的结果。
与TCC事务补偿机制相比,TCC由一个预留(Try)动作,相当于先保存一个草稿,然后提交;Saga事务没有预留动作,直接提交。
8. SAGA的恢复策略
对于事务异常,Saga提供了两种恢复策略,分别如下:
- 向后恢复:当执行事务失败时,补偿所有已完成的事务,是“一退到底”的方式,这种做法的效果是撤销之前所有成功的子事务,使得整个Saga的执行结果撤销。
- 向前恢复:对于不通过的事务,会尝试重试事务,这里有一个假设就是每个子事务最终都会成功,这种方式适用于必须成功的场景,事务失败了重试,不需要补偿。
9. SAGA事务的实现方式
- 命令协调:中央协调器以命令/回复的方式与每项服务进行通信,全权负责告诉每个参与者该做什么以及什么时候该做什么。中央处理器OSO必须事先知道执行整个事务所需的流程,如果由任何失败,它还负责通过向每个参与者发送命令撤销之前的操作来协调分布式事务的回滚,基于中央协调器协调一切时,回滚要容易的多,因为协调器默认时执行正向流程,回滚时只要执行反向流程即可。
- 事件编排:命令协调方式基于中央协调器实现,所以由单点风险,但是事件编排方式没有中央协调器,事件编排的实现方式中,每个服务产生自己的事件并监听其他服务的事件来决定是否应采取行动。在事件编排方法中,第一个服务执行一个事务,然后发布一个事件,该事件被一个或多个服务进行监听,着行服务在执行本地事务并发布(或不发布)新的事件。当最后一个服务执行本地事务并且不发布任何事件时,意味着分布式事务结束,或者它发布的事件没有任何Saga参与者监听到都意味着事务结束。
10. SAGA事务的优缺点
- 命令协调设计的优缺点:
1.1 优点:服务之间关系简单,避免服务间循环依赖,因为Saga协调器会调用Saga参与者,但参与者不会调用协调器;程序开发简单,只需要执行命令/回复(其实回复消息也是一种事件消息),降低参与者的复杂性;易维护扩展,在添加新步骤时,事务复杂性保持线性,回滚更容易管理,更容易实施和测试。
1.2 缺点:中央协调器处理逻辑容易庞大复杂,导致难以维护;存在协调器单点故障风险。- 事件编排设计的优缺点
2.1 优点:避免中央协调器单点故障风险;当涉及的步骤较少时服务开发简单,容易实现。
2.2 缺点:服务之间存在循环依赖的风险;当设计的步骤较多,服务间关系混乱,难以追踪调试。由于Saga模型没有Prepare阶段,因此事务间不能保证隔离性。当多个Saga事务操作同一资源时,就会产生更新丢失、脏数据读取等问题,这时需要在业务层控制并发,例如:在应用层面加锁,或者应用层面预先冻结资源。
================ 最终一致性方案(本地事务状态表&MQ事务消息&最大努力通知方案) ===========
11. 本地事务状态表
本地事务状态表的核心思想就是将分布式事务拆分成本地事务进行处理,该方案中主要由两种角色:事务主动方和事务被动方。事务主动方需要额外新建事务消息表,并在本地事务中完成业务处理和记录事务消息,并轮询事务消息表的数据发送事务消息,事务被动方基于消息中间间消费事务消息表中的事务。
12. 本地事务状态表的执行流程
- 事务主动方在同一个本地事务中处理业务和写消息表操作;
- 事务主动方通过消息中间件,通知事务被动方处理事务消息。消息队列可以基于Kafka、RocketMQ消息队列,事务主动方主动写消息到消息队列,事务消费方消费并处理消息队列中的消息;
- 事务被动方通过消息中间件,通知事务主动方事务已处理的消息;
- 事务主动方接收中间件的消息,根性消息表的状态为已处理。
一些必要的容错处理如下:
- 当步骤1处理出错,由于还在事务主动方的本地事务中,直接回滚即可;
- 当步骤2、步骤3处理出错,由于事务主动方本地保存了信息,只需要轮询消息重新通过消息中间发送,通知事务被动方重新读取消息处理业务即可;
- 如果时业务上处理失败,业务被动方可以发消息各事务主动方回滚事务;
- 如果事务被动方已经消费了消息,事务主动方需要回滚事务的话,需要发消息通知事务主动方进行事务回滚。
13. 本地事务状态表的优缺点
- 优点:从应用开发的角度实现了消息数据的可靠性,消息数据的可靠性不依赖消息中间件,弱化了对MQ中间件特性的依赖;方案轻量,容易实现。
- 缺点:与具体的业务场景绑定,耦合性强,不可共用;消息数据与业务数据同库,占用业务系统资源;业务系统在使用关系型数据库的请款下,消息服务性能会受到关系型数据库并发性能的局限。
14. MQ事务消息
基于MQ的分布式事务本质上是对本地消息表的封装,整体流程与本地消息表一致,唯一不同的就是将本地消息表存在了MQ内部,而不是业务数据库中,由于将本地消息表存在MQ内部,那么MQ内部的处理尤为重要。
15. RocketMQ事务消息方案
在本地消息方案中,保证事务主动方写业务表数据和写消息表数据的一致性时基于数据库事务,而RocketMQ的事务消息对于普通MQ提供了2PC的提交接口,方案如下:
- 正常情况
1.1 发送方向MQ Server(MQ服务方)发送half消息;
1.2 MQ Server将消息持久化成功后,向发送方ack确认消息已经发送成功;
1.3 发送方开始执行本地事务逻辑;
1.4 发送方根据本地事务执行结果向MQ Server提交二次确认(commit或是rollback);
1.5 最终步骤:MQ Server如果收到的时commit操作,则将half消息标记为可投递,MQ订阅方最终收到该消息;若收到的是rollback操作则删除half消息,订阅方将不会收到该消息。- 异常情况:在断网或者应用重启等异常情况下,步骤4提交的二次确认超时未送达MQ Server,此时的处理逻辑如下:
2.1 MQ Server对该消息发起消息回查;
2.2 发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果;
2.3 发送方根据检查得到的本地事务的追钟状态再次提交二次确认;
2.4 最终步骤:MQ Server基于commit/rollback对消息进行投递或删除。
16. MQ事务消息的优缺点
- 优点:相比本地消息表方案,MQ事务方案消息数据独立存储,降低业务系统与消息系统之间的耦合;吞吐量大于使用本地消息表方案。
- 缺点:一次消息发送需要两次网络请求;业务处理服务需要实现消息状态回查接口。
15. 最大努力通知
最大努力通知也成为定期校对,是对MQ事务方案的进一步优化。它在事务主动方增加消息校对的接口,如果事务被动方没有接收到主动方发送的消息,此时可以调用事务主动方提供的消息校对接口主动获取。
在可靠消息事务中,事务主动方需要将消息发送出去,并且让接收方成功接收信息,这种可靠性发送是由事务主动方保证的;但是最大努力通知,事务主动方仅仅是尽最大努力(重试,轮询…)将事务发送给事务接收方,所以存在事务被动方接收不到消息的情况,此时需要事务被动方主动调用事务主动方的消息校验接口查询业务消息并消费,这种通知的可靠性是由事务被动方保证的。
所以最大努力通知适用于业务通知类型,例如微信交易的结果,就是最大努力通知方式通知各个商户,既有回调通知,也有交易查询接口
============================== 各方案常见使用场景总结 ========================
2PC/3PC:依赖于数据库,能够很好的提供强一致性和强事务性,但延迟比较高,比较适合传统的单体应用,在同一个方法中存在跨库操作的情况,不适合高并发和高性能要求的场景。
TCC:适用于执行时间确定且较短,实时性要求高,对数据一致性要求高,比如互联网金融企业最核心的三个服务:交易、支付、账务。
Saga事务:由于Saga事务不能保证隔离性,需要在业务层控制并发,适用于业务场景事务并发操作同一资源较少的情况。Saga由于缺少预提交动作,导致补偿动作的先比较麻烦,例如业务时发送短信,补偿动作则得在发送一次短信说明撤销,用户体验比较差。所以,Saga事务较适用于补偿动作容易处理的场景。
本地事务表/MQ事务:适用于事务参与方支持操作幂等,对一致性要求不高,业务上能容忍数据不一致到一个人工检查周期,事务涉及的参与方、参与环节较少,业务上由对账/校验系统兜底。