分布式事务,两阶段提交协议,三阶段提交协议
一 分布式中的CAP怎么理解
1 CAP定义
C(Consistency)一致性 指的是所有节点在同一时间的数据完全一致
A(Availability)可用性 指服务一直可用,而且是正常响应时间
P(Partition Tolerance)分区容忍性 指在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务
大部分人常常简单的表述为:"一致性、可用性、分区容忍性三者你只能同时达到其中两个,不可能同时达到"。实际上这是具有误导性的说法。
当发生网络分区的时候,如果我们要继续服务,那么强一致性和可用性只能 2 选 1。也就是说当网络分区之后 P 是前提,决定了 P 之后才有 C 和 A 的选择。也就是说分区容错性我们是必须要实现的。
简而言之就是:CAP 理论中分区容错性 P 是一定要满足的,在此基础上,只能满足可用性 A 或者一致性 C。
因此,P是必须的,分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。 比如ZooKeeper, HBase就是CP架构,Eureka 就是 AP 架构,Nacos 不仅支持 CP 架构也支持 AP 架构。
为啥不可选择CA架构呢? 举个例子:若系统出现“分区”,系统中的某个节点在进行写操作。为了保证C, 必须要禁止其他节点的读写操作,这就和A发生冲突了。如果为了保证A,其他节点的读写操作正常的话就和C冲突了
选择CP还是AP的关键在于当前的业务场景,没有定论,比如对于需要确保强一致性的场景如银行一般会选择保证 CP 。
另外,需要补充说明的一点是:如果网络分区正常的话(系统在绝大部分时候所处的状态),也就说不需要保证 P 的时候,C和A能够同时保证。
2 CAP组合场景
CA: 放弃分区容错性。非分布式架构,比如关系数据库,因为没有分区
AP: 放弃强一致性。追求最终一致性,比如Eureka. 场景类似延时转账,可以接受两小时后转账
CP: 放弃可用性。zk在leader宕机选举期间不提供服务。场景类似立即转账, 必须一进一出 (zk是CP,任何时刻对ZooKeeper的访问请求能得到一致的数据结果,同时系统对网络分割具备容错性)
结论:在分布式系统中AP运用的最多,因为放弃的是强一致性,追求的是最终一致性,性价比最高
二 分布式中的BASE怎么理解
BASE 是 Basically Available(基本可用)、Soft-state(软状态) 和 Eventually Consistent(最终一致性) 三个短语的缩写。BASE 理论是对 CAP 中一致性 C 和可用性 A 权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于 CAP 定理逐步演化而来的,它大大降低了我们对系统的要求。
BASE 理论的核心思想
即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
也就是牺牲数据的一致性来满足系统的高可用性,系统中一部分数据不可用或者不一致时,仍需要保持系统整体“主要可用”。
BASE 理论本质上是对 CAP 的延伸和补充,更具体地说,是对 CAP 中 AP 方案的一个补充。
因此,AP 方案只是在系统发生分区的时候放弃一致性,而不是永远放弃一致性。在分区故障恢复后,系统应该达到最终一致性。这一点其实就是 BASE 理论延伸的地方。
1 基本可用
基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。但是,这绝不等价于系统不可用。
什么叫允许损失部分可用性呢?
- 响应时间上的损失: 正常情况下,处理用户请求需要0.5s返回结果,但是由于系统出现故障,处理用户请求的时间变为3s。
- 系统功能上的损失:正常情况下,用户可以使用系统的全部功能,但是由于系统访问量突然剧增,系统的部分非核心功能无法使用。
2 软状态
软状态指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
3 最终一致性
最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态,而不需要实时保证系统数据的强一致性。
三 分布式一致性 -> 二阶段提交2PC
1 引言
在分布式系统中,为了保证数据的高可用, 通常我们会将数据保留多个副本(replica), 这些副本会放置在不同的物理机器上。为了对用户提供正确的curd等语意,我们需要保证这些放置在不同物理机器上的副本是一致的。为了解决这种分布式一致性问题,提出了很多典型的协议和算法,比较著名的是二阶段提交协议(Two phaseCommit),三阶段提交协议和paxos算法。
在分布式系统中,各个节点之间在物理上相互独立,通过网络进行沟通和协调。由于存在事务机制,可以保证每个独立节点上的数据操作可以满足ACID。但是,相互独立的节点之间无法准确地知道其他节点的事务执行情况。所以从理论上来讲,两台机器无法达到一致的状态。如果想让分布式部署的多台机器中的数据保持一致性,那么就要保证在所有节点数据的写操作,要么全部都执行,要么全部都不执行。但是,一台机器在执行本地事务的时候无法知道其他机器中的本地事务的执行结果,所以它也就不知道本次事务到底应该commit还是rollback。所以,常规的解决办法就是引入一个"协调者"的组件来统一调度所有分布式节点的执行。
2 二阶段提交流程
这里说的两阶段提交,区别于网络上某些文章里提到的显然不实用的两阶段实现,是考虑到超时、异常恢复的两阶段提交。
1. 发起事务:事务的发起者提出一个请求(比如用户下单购买某个商品),要求其依赖服务(也就是事务的执行者)响应请求(比如通知优惠券业务锁定使用的优惠券、通知支付业务冻结付款金额、通知仓储服务冻结库存等等) 当所有依赖方都回复确认之后,事务的准备阶段完毕。
2. 确认/取消事务:当请求得到所有依赖服务的成功确认后,事务的发起者通知所有执行者确认(confirm)事务;如果第一步中只要有一个执行者返回失败,则取消(cancel)事务。
对于第二步,有些文章中的简单的二阶段提交是不需要执行者回复的,个人认为这意味着发起者无法确认第二步事务(无论是 confirm 还是 cancel 操作)有没有成功执行,所以个人认为需要确认。下文按有确认进行讨论。
更进一步,对于执行者而言,因为第一步的锁定返回了成功,所以第二步的确认只能是成功,不允许失败。执行者应想办法重试并保证成功。如果失败则意味着出现了系统的数据不一致。
3 超时处理
以上的流程在是理想情况下。当考虑到网络异常等情况,会存在三个问题:
-
对执行者而言:如果没有收到第二步事务,该如何处理?此时的执行者会一直锁定资源等待第二步事务。
-
对发起者而言:如果第一步中没有收到回复,该如果处理?此时的发起者无法得知是否所有执行者都成功锁定了资源。
-
对发起者而言:如果第二步中没有收到回复,该如果处理?此时的发起者无法得知是否所有执行者都成功确认了事务。
执行者没有收到第一步事务,对执行者而言是无感知的。所以没有这个问题。
依次回答这三个问题:
-
执行者没有收到第二步事务,有三种处理方案:第一种是一直锁定资源等待,第二种是超时 confirm 事务,第三种是超时 cancel 事务。对于两阶段提交而言,其他执行者在第一步是有可能返回失败的,所以显然强行 confirm 会有风险,第三种更为合理。此处有“应当 confirm 但因为网络或其他问题而没有收到,最终执行了超时 cancel”的风险,会导致数据不一致。
-
发起者第一步中没有收到回复,也存在两种策略:要么超时重试(再次提出事务),要么超时后当做返回失败处理。这两种可以组合使用,即多次超时重试后仍无回复则当做返回失败处理。
-
发起者第二步中没有收到回复,和问题 2 的处理策略类似,多次超时重试后仍无返回说明出现了异常,但不同的是这个异常是一个无法回滚的异常,意味着系统中可能出现了数据不一致,可能需要其他(很可能是人工)方式修复数据。
对于问题 3 ,因为在问题 1 的回答中我们默认执行者超时会 cancel 事务,所以当发起者第二步提出的是 cancel 时不会有什么问题。换句话说,当发起者在第二步提出 confirm 而没有收到回复时可能会出现数据不一致。
4 异常重试
当执行过程中发生异常(比如宕机),事务应当可以重试。
-
对发起者而言:如果在第一步发生异常:部分执行者锁定了资源,而另一部分从未收到过事务请求。由于执行者会默认超时 cancel,所以发起者发起 cancel 后(或不处理,直接等待超时)重新发起新事务即可。
-
对发起者而言:如果在第二步发生异常:如果执行的是 cancel,则无需重试,当做成功即可(当然也可以重试)。如果执行的是 confirm,则可能发生部分机器成功 confirm,部分机器由于没有收到 confirm,默认超时 cancel 请求,从而数据不一致的风险。
-
对执行者而言:如果在第一步发生异常:尽量返回失败即可,超时发起者会重试/cancel 请求。不会有什么风险。
-
对执行者而言:如果在第二步发生异常:尽量重试并保证成功。如果执行的是 confirm,说明第一步的锁定返回了成功,所以第二步的确认只能是成功。如果是 cancel,则更应当自行重试保证资源释放。
5 问题
- 单点故障问题,如果协调者挂了那么整个系统都处于不可用的状态了。
- 阻塞问题,即当协调者发送
prepare
请求,参与者收到之后如果能处理那么它将会进行事务的处理但并不提交,这个时候会一直占用着资源不释放,如果此时协调者挂了,那么这些资源都不会再释放了,这会极大影响性能。 - 数据不一致问题,比如当第二阶段,协调者只发送了一部分的
commit
请求就挂了,那么也就意味着,收到消息的参与者会进行事务的提交,而后面没收到的则不会进行事务提交,那么这时候就会产生数据不一致性问题。
四 分布式一致性 - 三阶段提交3PC
三阶段提交协议在协调者和参与者中都引入超时机制,并且把两阶段提交协议的第一个阶段分成了两步: 询问,然后再锁资源,最后真正提交。
1 三阶段的执行
(1) canCommit阶段
3PC的canCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回yes响应,否则返回no响应
(2) preCommit阶段
协调者根据参与者canCommit阶段的响应来决定是否可以继续事务的preCommit操作。根据响应情况,有下面两种可能:
a) 协调者从所有参与者得到的反馈都是yes:
那么进行事务的预执行,协调者向所有参与者发送preCommit请求,并进入prepared阶段。参与泽和接收到preCommit请求后会执行事务操作,并将undo和redo信息记录到事务日志中。如果一个参与者成功地执行了事务操作,则返回ACK响应,同时开始等待最终指令
b) 协调者从所有参与者得到的反馈有一个是No或是等待超时之后协调者都没收到响应:
那么就要中断事务,协调者向所有的参与者发送abort请求。参与者在收到来自协调者的abort请求,或超时后仍未收到协调者请求,执行事务中断。
(3) doCommit阶段
协调者根据参与者preCommit阶段的响应来决定是否可以继续事务的doCommit操作。根据响应情况,有下面两种可能:
a) 协调者从参与者得到了ACK的反馈:
协调者接收到参与者发送的ACK响应,那么它将从预提交状态进入到提交状态,并向所有参与者发送doCommit请求。参与者接收到doCommit请求后,执行正式的事务提交,并在完成事务提交之后释放所有事务资源,并向协调者发送haveCommitted的ACK响应。那么协调者收到这个ACK响应之后,完成任务。
b) 协调者从参与者没有得到ACK的反馈, 也可能是接收者发送的不是ACK响应,也可能是响应超时,此时执行事务中断。
2 2PC vs 3PC
对于协调者和参与者都设置了超时机制(在2PC中,只有协调者拥有超时机制,即如果在一定时间内没有收到cohort的消息则默认失败)。
在2PC的准备阶段和提交阶段之间,插入预提交阶段,使3PC拥有CanCommit、PreCommit、DoCommit三个阶段。
PreCommit是一个缓冲,保证了在最后提交阶段之前各参与节点的状态是一致的。
维基百科的一个通俗的解释:
三阶段提交在两阶段提交的第一阶段与第二阶段之间插入了一个准备阶段,
使得原先在两阶段提交中,参与者在投票之后,由于协调者发生崩溃或错误,而导致参与者处于无法知晓是否提交或者中止的“不确定状态”所产生的可能相当长的延时的问题得以解决。
举例来说,假设有一个决策小组由一个主持人负责与多位组员以电话联络方式协调是否通过一个提案,以两阶段提交来说,主持人收到一个提案请求,打电话跟每个组员询问是否通过并统计回复,然后将最后决定打电话通知各组员。
要是主持人在跟第一位组员通完电话后失忆,而第一位组员在得知结果并执行后老人痴呆,那么即使重新选出主持人,也没人知道最后的提案决定是什么,也许是通过,也许是驳回,
不管大家选择哪一种决定,都有可能与第一位组员已执行过的真实决定不一致,老板就会不开心认为决策小组沟通有问题而解雇。
三阶段提交即是引入了另一个步骤,主持人打电话跟组员通知请准备通过提案,以避免没人知道真实决定而造成决定不一致的失业危机。
为什么能够解决二阶段提交的问题呢?
回到刚刚提到的状况,在主持人通知完第一位组员请准备通过后两人意外失忆,即使没人知道全体在第一阶段的决定为何,全体决策组员仍可以重新协调过程或直接否决,不会有不一致决定。
那么当主持人通知完全体组员请准备通过并得到大家的再次确定后进入第三阶段,
当主持人通知第一位组员请通过提案后两人意外失忆,这时候其他组员再重新选出主持人后,
仍可以知道目前至少是处于准备通过提案阶段,表示第一阶段大家都已经决定要通过了,此时便可以直接通过。
3 三阶段提交协议的缺点
总之,3PC
通过一系列的超时机制很好的缓解了阻塞问题,但是最重要的一致性并没有得到根本的解决,
比如在 DoCommit
阶段,当一个参与者收到了请求之后其他参与者和协调者挂了或者出现了网络分区,这个时候收到消息的参与者都会进行事务提交,这就会出现数据不一致性问题。
再比如进入PreCommit后,协调者发出的是abort请求,假设只有一个参与者收到并进行了abort操作,而其他对于系统状态未知的参与者会根据3PC选择继续Commit,此时系统状态发生不一致性。
所以,要解决一致性问题还需要靠 Paxos
算法 (Zookeeper采用的就是Paxos算法的改进)