常用分布式事务解决方案
出处:https://github.com/clsaa/Distributed-Transaction-Notes。
作者总结得很全面,做个笔记搬运。
一、 两阶段提交(2PC)
二阶段提交(Two-phaseCommit)是指,在计算机网络以及数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法(Algorithm)。通常,二阶段提交也被称为是一种协议(Protocol))。在分布式系统中,每个节点虽然可以知晓自己的操作时成功或者失败,却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。因此,二阶段提交的算法思路可以概括为:参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。
- 所谓的两个阶段是指:第一阶段:准备阶段(投票阶段)和第二阶段:提交阶段(执行阶段)。
1. 准备阶段
- 事务协调者(事务管理器)给每个参与者(资源管理器)发送Prepare消息,每个参与者要么直接返回失败(如权限验证失败),要么在本地执行事务,写本地的redo和undo日志,但不提交,到达一种“万事俱备,只欠东风”的状态。
- 一般讲准备阶段分为以下三个阶段
- 协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应。
- 参与者节点执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志。(注意:若成功这里其实每个参与者已经执行了事务操作)
- 各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个”同意”消息;如果参与者节点的事务操作实际执行失败,则它返回一个”中止”消息。
2. 提交阶段
- 如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源)
- 当协调者节点从所有参与者节点获得的相应消息都为”同意”时:
- 协调者节点向所有参与者节点发出”正式提交(commit)”的请求。
- 参与者节点正式完成操作,并释放在整个事务期间内占用的资源。
- 参与者节点向协调者节点发送”完成”消息。
- 协调者节点受到所有参与者节点反馈的”完成”消息后,完成事务。
- 如果任一参与者节点在第一阶段返回的响应消息为”中止”,或者 协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:
- 协调者节点向所有参与者节点发出”回滚操作(rollback)”的请求。
- 参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。
- 参与者节点向协调者节点发送”回滚完成”消息。
- 协调者节点受到所有参与者节点反馈的”回滚完成”消息后,取消事务。
3. 两阶段提交的缺陷
- 同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
- 单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
- 数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。
- 二阶段无法解决的问题:协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
二、 三阶段提交(3PC)
- 三阶段提交(Three-phase commit),也叫三阶段提交协议(Three-phase commit protocol),是二阶段提交(2PC)的改进版本。
1. 与两阶段提交的不同点
- 引入超时机制。同时在协调者和参与者中都引入超时机制。
- 在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。
也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。
- 相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。
2. CanCommit阶段
- 3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应
1.事务询问:协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。
2.响应反馈:参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No
3. PreCommit阶段
- 假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会进行事务的预执行。
- 发送预提交请求:协调者向参与者发送PreCommit请求,并进入Prepared阶段。
- 事务预提交:参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。
- 响应反馈:如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。
- 假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。
- 发送中断请求:协调者向所有参与者发送abort请求。
- 中断事务:参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。
4. doCommit阶段
- 执行提交
- 发送提交请求:协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。
- 事务提交:参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。
- 响应反馈:事务提交完之后,向协调者发送Ack响应。
- 完成事务:协调者接收到所有参与者的ack响应之后,完成事务。
- 中断事务:协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。
- 发送中断请求:协调者向所有参与者发送abort请求
- 事务回滚:参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。
- 反馈结果:参与者完成事务回滚之后,向协调者发送ACK消息
- 中断事务:协调者接收到参与者反馈的ACK消息之后,执行事务的中断。
在doCommit阶段,如果参与者无法及时接收到来自协调者的doCommit或者rebort请求时,会在等待超时之后,会继续进行事务的提交。(其实这个应该是基于概率来决定的,当进入第三阶段时,说明参与者在第二阶段已经收到了PreCommit请求,那么协调者产生PreCommit请求的前提条件是他在第二阶段开始之前,收到所有参与者的CanCommit响应都是Yes。(一旦参与者收到了PreCommit,意味他知道大家其实都同意修改了)所以,一句话概括就是,当进入第三阶段时,由于网络超时等原因,虽然参与者没有收到commit或者abort响应,但是他有理由相信:成功提交的几率很大。 )
三、 CAP定理
1. 标准分布式事务缺陷
- 效率非常低
- 全局事务方式下,全局事务管理器(TM)通过XA接口使用二阶段提交协议( 2PC )与资源层(如数据库)进行交互。使用全局事务,数据被Lock的时间跨整个事务,直到全局事务结束。
- 2PC 是反可伸缩模式,在事务处理过程中,参与者需要一直持有资源直到整个分布式事务结束。这样,当业务规模越来越大的情况下,2PC 的局限性就越来越明显,系统可伸缩性会变得很差。
- 与本地事务相比,XA 协议的系统开销相当大,因而应当慎重考虑是否确实需要分布式事务。而且只有支持 XA 协议的资源才能参与分布式事务。
2. CAP理论详解
- CAP原则是NOSQL数据库的基石。
- CAP原则又称CAP定理,指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。
- 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
- 可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
- 分区容忍性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。(容忍网络中断)
四、 BASE理论
BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的简写,BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。接下来我们着重对BASE中的三要素进行详细讲解。
- BA: Basic Availability 基本业务可用性(支持分区失败):分布式系统在出现不可预知故障的时候,允许损失部分可用性
- 响应时间上的损失:正常情况下,一个在线搜索引擎需要0.5秒内返回给用户相应的查询结果,但由于出现异常(比如系统部分机房发生断电或断网故障),查询结果的响应时间增加到了1~2秒。
- 功能上的损失:正常情况下,在一个电子商务网站上进行购物,消费者几乎能够顺利地完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。
- S: Soft state 柔性状态(状态允许有短时间不同步,异步)
- E: Eventual consistency 最终一致性(最终数据是一致的,但不是实时一致)
- 因果一致性:因果一致性是指,如果进程A在更新完某个数据项后通知了进程B,那么进程B之后对该数据项的访问都应该能够获取到进程A更新后的最新值,并且如果进程B要对该数据项进行更新操作的话,务必基于进程A更新后的最新值,即不能发生丢失更新情况。与此同时,与进程A无因果关系的进程C的数据访问则没有这样的限制。
- 读己之所写:读己之所写是指,进程A更新一个数据项之后,它自己总是能够访问到更新过的最新值,而不会看到旧值。也就是说,对于单个数据获取者而言,其读取到的数据一定不会比自己上次写入的值旧。因此,读己之所写也可以看作是一种特殊的因果一致性。
- 会话一致性:会话一致性将对系统数据的访问过程框定在了一个会话当中:系统能保证在同一个有效的会话中实现“读己之所写”的一致性,也就是说,执行更新操作之后,客户端能够在同一个会话中始终读取到该数据项的最新值。
- 单调读一致性:单调读一致性是指如果一个进程从系统中读取出一个数据项的某个值后,那么系统对于该进程后续的任何数据访问都不应该返回更旧的值。
- 单调写一致性:单调写一致性是指,一个系统需要能够保证来自同一个进程的写操作被顺序地执行。
原子性(A)与持久性(D)必须根本保障
为了可用性、性能与降级服务的需要,只有降低一致性( C ) 与 隔离性( I ) 的要求
五、 柔性事务
1. 柔性事务中的服务模式
服务模式是柔性事务流程中的特殊操作实现(实现上对应业务服务要提供相应模式的功能接口),还不算是某一种柔性事务解决方案。
- 可查询操作
- 服务操作的可标识性
- 服务操作具有全局唯一标识(可以使用业务单据号/使用系统分配的操作流水号使用操作资源的组合组合标识)
- 操作有唯一的、确定的时间
- 单笔查询
- 使用全局唯一的服务操作标识,查询操作执行结果
- 注意状态判断,小心“处理中”的状态
- 批量查询
- 使用时间区段与(或)一组服务操作的标识,查询一批操作执行结果
- 服务操作的可标识性
- 幂等操作
- 幂等性:f(f(x)) = f(x)
- 幂等操作:重复调用多次产生的业务结果与调用一次产生的业务结果相同
- 实现方式一:通过业务操作本身实现幂等性
- 实现方式二:系统缓存所有请求与处理结果,检测到重复请求之后,自动返回之前的处理结果
- TCC操作
- Try: 尝试执行业务
- 完成所有业务检查(一致性)
- 预留必须业务资源(准隔离性)
- Confirm:确认执行业务
- 真正执行业务
- 不作任何业务检查
- 只使用Try阶段预留的业务资源
- Confirm操作要满足幂等性
- Cancel: 取消执行业务
- 释放Try阶段预留的业务资源
- Cancel操作要满足幂等性
- 与2PC协议比较
- 位于业务服务层而非资源层
- 没有单独的准备(Prepare)阶段,Try操作兼备资源操作与准备能力
- Try操作可以灵活选择业务资源的锁定粒度(以业务定粒度)
- 较高开发成本
- Try: 尝试执行业务
很多人把两阶段型操作等同于两阶段提交协议2PC操作。其实TCC操作也属于两阶段型操作。
- 可补偿操作
- do: 真正执行业务
- 完成业务处理
- 业务执行结果外部可见
- compensate:业务补偿
- 抵销(或部分抵销)正向业务操作的业务结果
- 补偿操作满足幂等性
- 约束
- 补偿在业务上可行
- 由于业务执行结果未隔离、或者补偿不完整带来的风险与成本可控
- do: 真正执行业务
TCC操作中的Confirm操作和Cancel操作,其实也可以看作是补偿操作
简而言之,TCC是应用层的2PC(2 Phase Commit, 两阶段提交),如果你将应用看做资源管理器的话。
详细来说,TCC每项操作需要做的事情如下:Try:尝试执行业务, 完成所有业务检查(一致性)预留必须业务资源(准隔离性); Confirm:确认执行业务, 真正执行业务不做任何业务检查只使用Try阶段预留的业务资源; Cancel:取消执行业务释放Try阶段预留的业务资源.
TCC事务的优点如下:解决了跨应用业务操作的原子性问题,在诸如组合支付、账务拆分场景非常实用。TCC实际上把数据库层的二阶段提交上提到了应用层来实现,对于数据库来说是一阶段提交,规避了数据库层的2PC性能低下问题。 TCC事务的缺点,主要就一个:TCC的Try、Confirm和Cancel操作功能需业务提供,开发成本高。
账务拆分的业务场景如下,分别位于三个不同分库的帐户A、B、C,A和B一起向C转帐共80元:
Try:尝试执行业务。完成所有业务检查(一致性):检查A、B、C的帐户状态是否正常,帐户A的余额是否不少于30元,帐户B的余额是否不少于50元。预留必须业务资源(准隔离性):帐户A的冻结金额增加30元,帐户B的冻结金额增加50元,这样就保证不会出现其他并发进程扣减了这两个帐户的余额而导致在后续的真正转帐操作过程中,帐户A和B的可用余额不够的情况。
Confirm:确认执行业务。真正执行业务:如果Try阶段帐户A、B、C状态正常,且帐户A、B余额够用,则执行帐户A给账户C转账30元、帐户B给账户C转账50元的转帐操作。不做任何业务检查:这时已经不需要做业务检查,Try阶段已经完成了业务检查。只使用Try阶段预留的业务资源:只需要使用Try阶段帐户A和帐户B冻结的金额即可。
Cancel:取消执行业务释放Try阶段预留的业务资源:如果Try阶段部分成功,比如帐户A的余额够用,且冻结相应金额成功,帐户B的余额不够而冻结失败,则需要对帐户A做Cancel操作,将帐户A被冻结的金额解冻掉。 小结:到底要不要使用TCC 到底要不要使用TCC事务,取决于以下几点:是否真正有保证跨应用业务操作的原子性需求。研发上能否投入资源开发相对应的TCC接口。
2. 最终一致性(可靠消息/异步确保型)
最终一致性是柔性事务解决方案。
- 实现
- 业务处理服务在业务事务提交前,向实时消息服务请求发送消息,实时消息服务只记录消息数据,而不真正发送。业务处理服务在业务事务提交后,向实时消息服务确认发送。只有在得到确认发送指令后,实时消息服务才真正发送
- 业务处理服务在业务事务回滚后,向实时消息服务取消发送。消息状态确认系统定期找到未确认发送或回滚发送的消息,向业务处理服务询问消息状态,业务处理服务根据消息ID或消息内容确定该消息是否有效
- 约束
- 被动方的处理结果不影响主动方的处理结果, 被动方的消息处理操作是幂等操作
- 成本
- 可靠消息系统建设成本
- 一次消息发送需要两次请求,业务处理服务需实现消息状态回查接口
- 优点、适用范围
- 消息数据独立存储、独立伸缩,降低业务系统与消息系统间的耦合
- 对最终一致性时间敏感度较高,降低业务被动方实现成本
- 用到的服务模式:可查询操作、幂等操作
- 方案特点
- 兼容所有实现JMS标准的MQ中间件
- 确保业务数据可靠的前提下,实现业务数据的最终一致(理想状态下基本是准实时一致)
3. TCC(两阶段型/补偿型)
TCC(两阶段型/补偿型):Try-Confirm-Cancel,柔性事务解决方案。
- 实现
- 一个完整的业务活动由一个主业务服务与若干从业务服务组成
- 主业务服务负责发起并完成整个业务活从业务服务提供TCC型业务操作
- 业务活动管理器控制业务活动的一致性,它登记业务活动中的操作, 并在业务活动提交时确认所有的TCC型操作的confirm操作,在业务活动取消时调用所有TCC型操作的cancel操作
- 成本
- 实现TCC操作的成本
- 业务活动结束时confirm或cancel操作的执行成本
- 业务活动日志成本
- 适用范围
- 强隔离性、严格一致性要求的业务活动
- 适用于执行时间较短的业务(比如处理账户、收费等业务)
- 用到的服务模式:TCC操作、幂等操作、可补偿操作、 可查询操作
- 方案特点:
- 不与具体的服务框架耦合(在RPC架构中通用)
- 位于业务服务层,而非资源层
- 可以灵活选择业务资源的锁定粒度
- TCC里对每个服务资源操作的是本地事务,数据被lock的时间短,可扩展性好(可以说是为独立部署的SOA服务而设计的)
4. 最大努力通知型(定期校对)
柔性事务解决方案。
- 实现
- 业务活动的主动方,在完成业务处理之后,向业务活动的被动方发送消息,允许消息丢失。
- 业务活动的被动方根据定时策略,向业务活动主动方查询,恢复丢失的业务消息。
- 约束:被动方的处理结果不影响主动方的处理结果
- 成本:业务查询与校对系统的建设成本
- 适用范围
- 对业务最终一致性的时间敏感度低
- 跨企业的业务活动
- 用到的服务模式:可查询操作
- 方案特点
- 业务活动的主动方在完成业务处理后,向业务活动被动方发送通知消息(允许消息丢失)
- 主动方可以设置时间阶梯型通知规则,在通知失败后按规则重复通知,直到通知N次后不再通知
- 主动方提供校对查询接口给被动方按需校对查询,用于恢复丢失的业务消息
- 行业应用案例:银行通知、商户通知等
六、 总结
1.分类总结
- 刚性事务
- XA 全局事务(标准的分布式事务)
- 柔性事务
- 可靠消息最终一致(异步确保型)
- TCC(两阶段型、补偿型)
- 最大努力通知(非可靠消息 、 定期校对)
- 纯补偿型(略)