共计 8869 个字符,预计需要花费 23 分钟才能阅读完成。
分布式事务:在分布式系统中一次操作须要由多个服务协同实现,这种由不同的服务之间通过网络协同实现的事务称为分布式事务
一、2PC:
2PC,两阶段提交,将事务的提交过程分为资源筹备和资源提交两个阶段,并且由事务协调者来协调所有事务参与者,如果筹备阶段所有事务参与者都预留资源胜利,则进行第二阶段的资源提交,否则事务协调者回滚资源。
1、第一阶段:筹备阶段
由事务协调者询问告诉各个事务参与者,是否筹备好了执行事务,具体流程图如下:
- ① 协调者向所有参与者发送事务内容,询问是否能够提交事务,并期待回答
- ② 各参与者执行本地事务操作,将 undo 和 redo 信息记入事务日志中(但不提交事务)
- ③ 如参与者执行胜利,给协调者反馈批准,否则反馈停止,示意事务不能够执行
2、第二阶段:提交阶段
协调者收到各个参与者的筹备音讯后,依据反馈状况告诉各个参与者 commit 提交或者 rollback 回滚
(1)事务提交:
当第一阶段所有参与者都反馈批准时,协调者发动正式提交事务的申请,当所有参与者都回复批准时,则意味着实现事务,具体流程如下:
- ① 协调者节点向所有参与者节点收回正式提交的 commit 申请。
- ② 收到协调者的 commit 申请后,参与者正式执行事务提交操作,并开释在整个事务期间内占用的资源。
- ③ 参与者实现事务提交后,向协调者节点发送 ACK 音讯。
- ④ 协调者节点收到所有参与者节点反馈的 ACK 音讯后,实现事务。
所以,失常提交时,事务的残缺流程图如下:
(2)事务回滚:
如果任意一个参与者节点在第一阶段返回的音讯为停止,或者协调者节点在第一阶段的询问超时之前无奈获取所有参与者节点的响应音讯时,那么这个事务将会被回滚,具体流程如下:
- ① 协调者向所有参与者收回 rollback 回滚操作的申请
- ② 参与者利用阶段一写入的 undo 信息执行回滚,并开释在整个事务期间内占用的资源
- ③ 参与者在实现事务回滚之后,向协调者发送回滚实现的 ACK 音讯
- ④ 协调者收到所有参与者反馈的 ACK 音讯后,勾销事务
所以,事务回滚时,残缺流程图如下:
3、2PC 的毛病:
二阶段提交的确可能提供原子性的操作,然而可怜的是,二阶段提交还是有几个毛病的:
(1)性能问题:执行过程中,所有参加节点都是事务阻塞性的,当参与者占有公共资源时,其余第三方节点拜访公共资源就不得不处于阻塞状态,为了数据的一致性而就义了可用性,对性能影响较大,不适宜高并发高性能场景
(2)可靠性问题:2PC 十分依赖协调者,当协调者产生故障时,尤其是第二阶段,那么所有的参与者就会都处于锁定事务资源的状态中,而无奈持续实现事务操作(如果是协调者挂掉,能够从新选举一个协调者,然而无奈解决因为协调者宕机导致的参与者处于阻塞状态的问题)
(3)数据一致性问题:在阶段二中,当协调者向参与者发送 commit 申请之后,产生了部分网络异样或者在发送 commit 申请过程中协调者产生了故障,这回导致只有一部分参与者承受到了 commit 申请。而在这部分参与者接到 commit 申请之后就会执行 commit 操作。然而其余局部未接到 commit 申请的机器则无奈执行事务提交。于是整个分布式系统便呈现了数据部一致性的景象。
(4)二阶段无奈解决的问题:协调者在收回 commit 音讯之后宕机,而惟一接管到这条音讯的参与者同时也宕机了,那么即便协调者通过选举协定产生了新的协调者,这条事务的状态也是不确定的,没人晓得事务是否被曾经提交。
二、3PC:
3PC,三阶段提交协定,是二阶段提交协定的改良版本,三阶段提交有两个改变点:
- (1)在协调者和参与者中都引入超时机制
- (2)在第一阶段和第二阶段中插入一个筹备阶段,保障了在最初提交阶段之前各参加节点的状态是统一的。
所以 3PC 会分为 3 个阶段,CanCommit 筹备阶段、PreCommit 预提交阶段、DoCommit 提交阶段,解决流程如下:
1、阶段一:CanCommit 筹备阶段
协调者向参与者发送 canCommit 申请,参与者如果能够提交就返回 Yes 响应,否则返回 No 响应,具体流程如下:
- (1)事务询问:协调者向所有参与者收回蕴含事务内容的 canCommit 申请,询问是否能够提交事务,并期待所有参与者回答。
- (2)响应反馈:参与者收到 canCommit 申请后,如果认为能够执行事务操作,则反馈 yes 并进入准备状态,否则反馈 no。
2、阶段二:PreCommit 阶段
协调者依据参与者的反馈状况来决定是否能够进行事务的 PreCommit 操作。依据响应状况,有以下两种可能:
(1)执行事务:
如果所有参与者均反馈 yes,协调者预执行事务,具体如下:
- ① 发送预提交申请:协调者向参与者发送 PreCommit 申请,并进入筹备阶段
- ② 事务预提交:参与者接管到 PreCommit 申请后,会执行本地事务操作,并将 undo 和 redo 信息记录到事务日志中(但不提交事务)
- ③ 响应反馈:如果参与者胜利的执行了事务操作,则返回 ACK 响应,同时开始期待最终指令。
(2)中断事务:
如果有任何一个参与者向协调者发送了 No 响应,或者期待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断,流程如下:
- ① 发送中断请求:协调者向所有参与者发送 abort 申请。
- ② 中断事务:参与者收到来自协调者的 abort 申请之后(或超时之后,仍未收到协调者的申请),执行事务的中断。
3、阶段三:doCommit 阶段
该阶段进行真正的事务提交,也能够分为以下两种状况:
(1)提交事务:
- ① 发送提交申请:协调接管到所有参与者发送的 ACK 响应,那么他将从预提交状态进入到提交状态,并向所有参与者发送 doCommit 申请
- ② 本地事务提交:参与者接管到 doCommit 申请之后,执行正式的事务提交,并在实现事务提交之后开释所有事务资源
- ③ 响应反馈:事务提交完之后,向协调者发送 ack 响应。
- ④ 实现事务:协调者接管到所有参与者的 ack 响应之后,实现事务。
(2)中断事务:任何一个参与者反馈 no,或者期待超时后协调者尚无奈收到所有参与者的反馈,即中断事务
- ① 发送中断请求:如果协调者处于工作状态,向所有参与者收回 abort 申请
- ② 事务回滚:参与者接管到 abort 申请之后,利用其在阶段二记录的 undo 信息来执行事务的回滚操作,并在实现回滚之后开释所有的事务资源。
- ③ 反馈后果:参与者实现事务回滚之后,向协调者反馈 ACK 音讯
- ④ 中断事务:协调者接管到参与者反馈的 ACK 音讯之后,执行事务的中断。
进入 doCommit 阶段后,无论协调者呈现问题,或者协调者与参与者之间的网络呈现问题,都会导致参与者无奈接管到协调者收回的 doCommit 申请或 abort 申请。此时,参与者都会在期待超时之后,继续执行事务提交。这其实基于概率来决定的,当进入第三阶段时,阐明第一阶段收到所有参与者的 CanCommit 响应都是 Yes,意味着大家都批准批改了,并且第二阶段所有的参与者对协调者的 PreCommit 申请也都是批准的。所以,一句话概括就是,当进入第三阶段时,因为网络超时等起因,尽管参与者没有收到 commit 或者 abort 响应,然而他有理由置信:胜利提交的几率很大。
4、3PC 的优缺点:
与 2PC 相比,3PC 升高了阻塞范畴,并且在期待超时后,协调者或参与者会中断事务,防止了协调者单点问题,阶段三中协调者呈现问题时,参与者会持续提交事务。
数据不统一问题仍然存在,当在参与者收到 preCommit 申请后期待 doCommit 指令时,此时如果协调者申请中断事务,而协调者因为网络问题无奈与参与者失常通信,会导致参与者持续提交事务,造成数据不统一。
2PC 和 3PC 都无奈保证数据相对的一致性,个别为了预防这种问题,能够增加一个报警,比方监控到事务异样的时候,通过脚本主动弥补差别的信息。
三、TCC:
1、什么是 TCC:
TCC(Try Confirm Cancel)是应用层的两阶段提交,所以对代码的侵入性强,其核心思想是:针对每个操作,都要实现对应的确认和弥补操作,也就是业务逻辑的每个分支都须要实现 try、confirm、cancel 三个操作,第一阶段由业务代码编排来调用 Try 接口进行资源预留,当所有参与者的 Try 接口都胜利了,事务协调者提交事务,并调用参与者的 confirm 接口真正提交业务操作,否则调用每个参与者的 cancel 接口回滚事务,并且因为 confirm 或者 cancel 有可能会重试,因而对应的局部须要反对幂等。
2、TCC 的执行流程:
TCC 的执行流程能够分为两个阶段,别离如下:
(1)第一阶段:Try,业务零碎做检测并预留资源 (加锁,锁住资源),比方常见的下单,在 try 阶段,咱们不是真正的减库存,而是把下单的库存给锁定住。
(2)第二阶段:依据第一阶段的后果决定是执行 confirm 还是 cancel
- Confirm:执行真正的业务(执行业务,开释锁)
- Cancle:是对 Try 阶段预留资源的开释(出问题,开释锁)
3、TCC 如何保障最终一致性:
- TCC 事务机制以 Try 为核心的,Confirm 确认操作和 Cancel 勾销操作都是围绕 Try 而开展。因而,Try 阶段中的操作,其保障性是最好的,即便失败,依然有 Cancel 勾销操作能够将其执行后果撤销。
- Try 阶段执行胜利并开始执行 Confirm 阶段时,默认 Confirm 阶段是不会出错的,也就是说只有 Try 胜利,Confirm 肯定胜利(TCC 设计之初的定义)
- Confirm 与 Cancel 如果失败,由 TCC 框架进行重试弥补
- 存在极低概率在 CC 环节彻底失败,则须要定时工作或人工染指
4、TCC 的注意事项:
(1)容许空回滚:
空回滚呈现的起因是 Try 超时或者丢包,导致 TCC 分布式事务二阶段的 回滚,触发 Cancel 操作,此时事务参与者未收到 Try,然而却收到了 Cancel 申请,如下图所示:
所以 cancel 接口在实现时须要容许空回滚,也就是 Cancel 执行时如果发现没有对应的事务 xid 或主键时,须要返回回滚胜利,让事务服务管理器认为已回滚。
(2)防悬挂管制:
悬挂指的是二阶段的 Cancel 比 一阶段的 Try 操作先执行,呈现该问题的起因是 Try 因为网络拥挤而超时,导致事务管理器生成回滚,触发 Cancel 接口,但之后拥挤在网络的 Try 操作又被资源管理器收到了,然而 Cancel 比 Try 先到。但依照后面容许空回滚的逻辑,回滚会返回胜利,事务管理器认为事务已回滚胜利,所以此时应该拒绝执行空回滚之后到来的 Try 操作,否则会产生数据不统一。因而咱们能够在 Cancel 空回滚返回胜利之前,先记录该条事务 xid 或业务主键,标识这条记录曾经回滚过,Try 接口执行前先查看这条事务 xid 或业务主键是否曾经标记为回滚胜利,如果是则不执行 Try 的业务操作。
(3)幂等管制:
因为网络起因或者重试操作都有可能导致 Try – Confirm – Cancel 3 个操作的反复执行,所以应用 TCC 时须要留神这三个操作的幂等管制,通常咱们能够应用事务 xid 或业务主键判重来管制。
5、TCC 计划的优缺点:
(1)TCC 事务机制相比于下面介绍的 XA 事务机制,有以下长处:
- 性能晋升:具体业务来实现,管制资源锁的粒度变小,不会锁定整个资源。
- 数据最终一致性:基于 Confirm 和 Cancel 的幂等性,保障事务最终实现确认或者勾销,保证数据的一致性。
- 可靠性:解决了 XA 协定的协调者单点故障问题,由主业务方发动并管制整个业务流动,业务流动管理器也变成多点,引入集群。
(2)毛病:TCC 的 Try、Confirm 和 Cancel 操作性能要按具体业务来实现,业务耦合度较高,进步了开发成本。
四、Saga 事务:
1、什么是 Saga 事务:
Saga 事务核心思想是将长事务拆分为多个本地短事务并顺次失常提交,如果所有短事务均执行胜利,那么分布式事务提交;如果呈现某个参与者执行本地事务失败,则由 Saga 事务协调器协调依据相同顺序调用弥补操作,回滚已提交的参与者,使分布式事务回到最初始的状态。Saga 事务根本协定如下:
- (1)每个 Saga 事务由一系列幂等的有序子事务(sub-transaction) Ti 组成。
- (2)每个 Ti 都有对应的幂等弥补动作 Ci,弥补动作用于撤销 Ti 造成的后果。
与 TCC 事务弥补机制相比,TCC 有一个预留 (Try) 动作,相当于先报存一个草稿,而后才提交;Saga 事务没有预留动作,间接提交。
2、Saga 的复原策略:
对于事务异样,Saga 提供了两种复原策略,别离如下:
(1)向后复原(backward recovery):
当执行事务失败时,弥补所有已实现的事务,是“一退到底”的形式,这种做法的成果是撤销掉之前所有胜利的子事务,使得整个 Saga 的执行后果撤销。如下图:
从上图可知事务执行到了领取事务 T3,然而失败了,因而事务回滚须要从 C3,C2,C1 顺次进行回滚弥补,对应的执行程序为:T1,T2,T3,C3,C2,C1。
(2)向前复原(forward recovery):
对于执行不通过的事务,会尝试重试事务,这里有一个假如就是每个子事务最终都会胜利,这种形式实用于必须要胜利的场景,事务失败了重试,不须要弥补。流程如下图:
3、Saga 事务的实现形式:
Saga 事务有两种不同的实现形式,别离如下:
- 命令协调(Order Orchestrator)
- 事件编排(Event Choreographyo)
(1)命令协调:
地方协调器(Orchestrator,简称 OSO)以命令 / 回复的形式与每项服务进行通信,全权负责通知每个参与者该做什么以及什么时候该做什么。整体流程如下图:
- ① 事务发起方的主业务逻辑申请 OSO 服务开启订单事务
- ② OSO 向库存服务申请扣减库存,库存服务回复处理结果。
- ③ OSO 向订单服务申请创立订单,订单服务回复创立后果。
- ④ OSO 向领取服务申请领取,领取服务回复处理结果。
- ⑤ 主业务逻辑接管并解决 OSO 事务处理后果回复。
地方协调器 OSO 必须当时晓得执行整个事务所需的流程,如果有任何失败,它还负责通过向每个参与者发送命令来撤销之前的操作来协调分布式的回滚,基于地方协调器协调所有时,回滚要容易得多,因为协调器默认是执行正向流程,回滚时只有执行反向流程即可。
(2)事件编排:
命令协调形式基于地方协调器实现,所以有单点危险,然而事件编排形式没有地方协调器。事件编排的实现形式中,每个服务产生本人的工夫并监听其余服务的事件来决定是否应采取行动。
在事件编排办法中,第一个服务执行一个事务,而后公布一个事件,该事件被一个或多个服务进行监听,这些服务再执行本地事务并公布(或不公布)新的事件。当最初一个服务执行本地事务并且不公布任何事件时,意味着分布式事务完结,或者它公布的事件没有被任何 Saga 参与者听到都意味着事务完结。
- ① 事务发起方的主业务逻辑公布开始订单事件。
- ② 库存服务监听开始订单事件,扣减库存,并公布库存已扣减事件。
- ③ 订单服务监听库存已扣减事件,创立订单,并公布订单已创立事件。
- ④ 领取服务监听订单已创立事件,进行领取,并公布订单已领取事件。
- ⑤ 主业务逻辑监听订单已领取事件并解决。
如果事务波及 2 至 4 个步骤,则十分适合应用事件编排形式,它是实现 Saga 模式的天然形式,它很简略,容易了解,不须要太多的代码来构建。
4、Saga 事务的优缺点:
(1)命令协调设计的优缺点:
① 长处:
- 服务之间关系简略,防止服务间循环依赖,因为 Saga 协调器会调用 Saga 参与者,但参与者不会调用协调器。
- 程序开发简略,只须要执行命令 / 回复(其实回复音讯也是一种事件音讯),升高参与者的复杂性。
- 易保护扩大,在增加新步骤时,事务复杂性放弃线性,回滚更容易治理,更容易施行和测试。
② 毛病:
- 地方协调器解决逻辑容易变得宏大简单,导致难以保护。
- 存在协调器单点故障危险。
(2)事件编排设计的优缺点:
① 长处:
- 防止地方协调器单点故障危险。
- 当波及的步骤较少服务开发简略,容易实现。
② 毛病:
- 服务之间存在循环依赖的危险。
- 当波及的步骤较多,服务间关系凌乱,难以追踪调测。
因为 Saga 模型没有 Prepare 阶段,因而事务间不能保障隔离性。当多个 Saga 事务操作同一资源时,就会产生更新失落、脏数据读取等问题,这时须要在业务层管制并发,例如:在利用层面加锁,或者利用层面事后解冻资源。
五、本地音讯表:
1、什么是本地音讯表:
本地音讯表的外围思路就是将分布式事务拆分老本地事务进行解决,在该计划中次要有两种角色:事务被动方和事务被动方。事务被动发起方须要额定新建事务音讯表,并在本地事务中实现业务解决和记录事务音讯,并轮询事务音讯表的数据发送事务音讯,事务被动方基于消息中间件生产事务音讯表中的事务。
这样能够防止以下两种状况导致的数据不一致性:
- 业务解决胜利、事务音讯发送失败
- 业务解决失败、事务音讯发送胜利
2、本地音讯表的执行流程:
- ① 事务被动方在同一个本地事务中解决业务和写音讯表操作
- ② 事务被动方通过消息中间件,告诉事务被动方处理事务音讯。消息中间件能够基于 Kafka、RocketMQ 音讯队列,事务被动方被动写音讯到音讯队列,事务生产方生产并解决音讯队列中的音讯。
- ③ 事务被动方通过消息中间件,告诉事务被动方事务已解决的音讯。
- ④ 事务被动方接管中间件的音讯,更新音讯表的状态为已解决。
一些必要的容错解决如下:
- 当①解决出错,因为还在事务被动方的本地事务中,间接回滚即可
- 当②、③解决出错,因为事务被动方本地保留了音讯,只须要轮询音讯从新通过消息中间件发送,告诉事务被动方从新读取音讯解决业务即可。
- 如果是业务上解决失败,事务被动方能够发消息给事务被动方回滚事务
- 如果事务被动方曾经生产了音讯,事务被动方须要回滚事务的话,须要发消息告诉事务被动方进行回滚事务。
3、本地音讯表的优缺点:
(1)长处:
- 从利用设计开发的角度实现了音讯数据的可靠性,音讯数据的可靠性不依赖于消息中间件,弱化了对 MQ 中间件个性的依赖。
- 计划轻量,容易实现。
(2)毛病:
- 与具体的业务场景绑定,耦合性强,不可专用
- 音讯数据与业务数据同库,占用业务系统资源
- 业务零碎在应用关系型数据库的状况下,音讯服务性能会受到关系型数据库并发性能的局限
六、MQ 事务音讯:
1、MQ 事务音讯的执行流程:
基于 MQ 的分布式事务计划实质上是对本地音讯表的封装,整体流程与本地音讯表统一,惟一不同的就是将本地音讯表存在了 MQ 外部,而不是业务数据库中,如下图:
因为将本地音讯表存在了 MQ 外部,那么 MQ 外部的解决尤为重要,上面次要基于 RocketMQ4.3 之后的版本介绍 MQ 的分布式事务计划
2、RocketMQ 事务音讯:
在本地音讯表计划中,保障事务被动方发写业务表数据和写音讯表数据的一致性是基于数据库事务,而 RocketMQ 的事务音讯绝对于一般 MQ 提供了 2PC 的提交接口,计划如下:
(1)失常状况:
在事务被动方服务失常,没有产生故障的状况下,发消息流程如下:
- 步骤①:发送方向 MQ Server(MQ 服务方)发送 half 音讯
- 步骤②:MQ Server 将音讯长久化胜利之后,向发送方 ack 确认音讯曾经发送胜利
- 步骤③:发送方开始执行本地事务逻辑
- 步骤④:发送方依据本地事务执行后果向 MQ Server 提交二次确认(commit 或是 rollback)。
- 最终步骤:MQ Server 如果收到的是 commit 操作,则将半音讯标记为可投递,MQ 订阅方最终将收到该音讯;若收到的是 rollback 操作则删除 half 半音讯,订阅方将不会承受该音讯
(2)异常情况:
在断网或者利用重启等异常情况下,图中的步骤④提交的二次确认超时未达到 MQ Server,此时的解决逻辑如下:
- 步骤⑤:MQ Server 对该音讯发动音讯回查
- 步骤⑥:发送方收到音讯回查后,须要查看对应音讯的本地事务执行的最终后果
- 步骤⑦:发送方依据查看失去的本地事务的最终状态再次提交二次确认。
- 最终步骤:MQ Server 基于 commit/rollback 对音讯进行投递或者删除。
3、MQ 事务音讯的优缺点:
(1)长处:相比本地音讯表计划,MQ 事务计划长处是:
- 音讯数据独立存储,升高业务零碎与音讯零碎之间的耦合
- 吞吐量大于应用本地音讯表计划
(2)毛病:
- 一次音讯发送须要两次网络申请(half 音讯 + commit/rollback 音讯)。
- 业务解决服务须要实现音讯状态回查接口。
七、最大致力告诉:
最大致力告诉也称为定期校对,是对 MQ 事务计划的进一步优化。它在事务被动方减少了音讯校对的接口,如果事务被动方没有接管到被动方发送的音讯,此时能够调用事务被动方提供的音讯校对的接口被动获取
在可靠消息事务中,事务被动方须要将音讯发送进来,并且让接管方胜利接管音讯,这种可靠性发送是由事务被动方保障的;然而最大致力告诉,事务被动方仅仅是尽最大致力(重试,轮询 ….)将事务发送给事务接管方,所以存在事务被动方接管不到音讯的状况,此时须要事务被动方被动调用事务被动方的音讯校对接口查问业务音讯并生产,这种告诉的可靠性是由事务被动方保障的。
所以最大致力告诉实用于业务告诉类型,例如微信交易的后果,就是通过最大致力告诉形式告诉各个商户,既有回调告诉,也有交易查问接口。
八、各计划常见应用场景总结:
- 2PC/3PC:依赖于数据库,可能很好的提供强一致性和强事务性,但提早比拟高,比拟适宜传统的单体利用,在同一个办法中存在跨库操作的状况,不适宜高并发和高性能要求的场景。
- TCC:实用于执行工夫确定且较短,实时性要求高,对数据一致性要求高,比方互联网金融企业最外围的三个服务:交易、领取、账务。
- 本地音讯表 /MQ 事务:实用于事务中参与方反对操作幂等,对一致性要求不高,业务上能容忍数据不统一到一个人工查看周期,事务波及的参与方、参加环节较少,业务上有对账 / 校验零碎兜底。
- Saga 事务:因为 Saga 事务不能保障隔离性,须要在业务层管制并发,适宜于业务场景事务并发操作同一资源较少的状况。Saga 因为短少预提交动作,导致弥补动作的实现比拟麻烦,例如业务是发送短信,弥补动作则得再发送一次短信阐明撤销,用户体验比拟差。所以,Saga 事务较实用于弥补动作容易解决的场景