1. 2PC(两阶段提交)
两阶段提交是一种保证分布式系统数据一致性的协议,现在很多数据库都是采用的两阶段提交协议来完成 分布式事务 的处理。
在介绍2PC之前,我们先来想想分布式事务到底有什么问题呢?
还拿秒杀系统的下订单和加积分两个系统来举例吧(我想你们可能都吐了🤮🤮🤮),我们此时下完订单会发个消息给积分系统告诉它下面该增加积分了。如果我们仅仅是发送一个消息也不收回复,那么我们的订单系统怎么能知道积分系统的收到消息的情况呢?如果我们增加一个收回复的过程,那么当积分系统收到消息后返回给订单系统一个 Response
,但在中间出现了网络波动,那个回复消息没有发送成功,订单系统是不是以为积分系统消息接收失败了?它是不是会回滚事务?但此时积分系统是成功收到消息的,它就会去处理消息然后给用户增加积分,这个时候就会出现积分加了但是订单没下成功。
所以我们所需要解决的是在分布式系统中,整个调用链中,我们所有服务的数据处理要么都成功要么都失败,即所有服务的 原子性问题 。
在两阶段提交中,主要涉及到两个角色,分别是协调者和参与者。
第一阶段:当要执行一个分布式事务的时候,事务发起者首先向协调者发起事务请求,然后协调者会给所有参与者发送 prepare
请求(其中包括事务内容)告诉参与者你们需要执行事务了,如果能执行我发的事务内容那么就先执行但不提交,执行后请给我回复。然后参与者收到 prepare
消息后,他们会开始执行事务(但不提交),并将 Undo
和 Redo
信息记入事务日志中,之后参与者就向协调者反馈是否准备好了。
第二阶段:第二阶段主要是协调者根据参与者反馈的情况来决定接下来是否可以进行事务的提交操作,即提交事务或者回滚事务。
比如这个时候 所有的参与者 都返回了准备好了的消息,这个时候就进行事务的提交,协调者此时会给所有的参与者发送 Commit
请求 ,当参与者收到 Commit
请求的时候会执行前面执行的事务的 提交操作 ,提交完毕之后将给协调者发送提交成功的响应。
而如果在第一阶段并不是所有参与者都返回了准备好了的消息,那么此时协调者将会给所有参与者发送 回滚事务的 rollback
请求,参与者收到之后将会 回滚它在第一阶段所做的事务处理 ,然后再将处理情况返回给协调者,最终协调者收到响应后便给事务发起者返回处理失败的结果。
个人觉得 2PC 实现得还是比较鸡肋的,因为事实上它只解决了各个事务的原子性问题,随之也带来了很多的问题。
- 单点故障问题,如果协调者挂了那么整个系统都处于不可用的状态了。
- 阻塞问题,即当协调者发送
prepare
请求,参与者收到之后如果能处理那么它将会进行事务的处理但并不提交,这个时候会一直占用着资源不释放,如果此时协调者挂了,那么这些资源都不会再释放了,这会极大影响性能。 - 数据不一致问题,比如当第二阶段,协调者只发送了一部分的
commit
请求就挂了,那么也就意味着,收到消息的参与者会进行事务的提交,而后面没收到的则不会进行事务提交,那么这时候就会产生数据不一致性问题。
2. 3PC(三阶段提交)
因为2PC存在的一系列问题,比如单点,容错机制缺陷等等,从而产生了 3PC(三阶段提交) 。那么这三阶段又分别是什么呢?
千万不要吧PC理解成个人电脑了,其实他们是 phase-commit 的缩写,即阶段提交。
- CanCommit阶段:协调者向所有参与者发送
CanCommit
请求,参与者收到请求后会根据自身情况查看是否能执行事务,如果可以则返回 YES 响应并进入预备状态,否则返回 NO 。 - PreCommit阶段:协调者根据参与者返回的响应来决定是否可以进行下面的
PreCommit
操作。如果上面参与者返回的都是 YES,那么协调者将向所有参与者发送PreCommit
预提交请求,参与者收到预提交请求后,会进行事务的执行操作,并将Undo
和Redo
信息写入事务日志中 ,最后如果参与者顺利执行了事务则给协调者返回成功的响应。如果在第一阶段协调者收到了 任何一个 NO 的信息,或者 在一定时间内 并没有收到全部的参与者的响应,那么就会中断事务,它会向所有参与者发送中断请求(abort),参与者收到中断请求之后会立即中断事务,或者在一定时间内没有收到协调者的请求,它也会中断事务。 - DoCommit阶段:这个阶段其实和
2PC
的第二阶段差不多,如果协调者收到了所有参与者在PreCommit
阶段的 YES 响应,那么协调者将会给所有参与者发送DoCommit
请求,参与者收到DoCommit
请求后则会进行事务的提交工作,完成后则会给协调者返回响应,协调者收到所有参与者返回的事务提交成功的响应之后则完成事务。若协调者在PreCommit
阶段 收到了任何一个 NO 或者在一定时间内没有收到所有参与者的响应 ,那么就会进行中断请求的发送,参与者收到中断请求后则会 通过上面记录的回滚日志 来进行事务的回滚操作,并向协调者反馈回滚状况,协调者收到参与者返回的消息后,中断事务。
这里是
3PC
在成功的环境下的流程图,你可以看到3PC
在很多地方进行了超时中断的处理,比如协调者在指定时间内为收到全部的确认消息则进行事务中断的处理,这样能 减少同步阻塞的时间 。还有需要注意的是,**3PC
在DoCommit
阶段参与者如未收到协调者发送的提交事务的请求,它会在一定时间内进行事务的提交。为什么这么做呢?是因为这个时候我们肯定保证了在第一阶段所有的协调者全部返回了可以执行事务的响应,这个时候我们有理由相信其他系统都能进行事务的执行和提交,所以不管**协调者有没有发消息给参与者,进入第三阶段参与者都会进行事务的提交操作。
总之,3PC
通过一系列的超时机制很好的缓解了阻塞问题,但是最重要的一致性并没有得到根本的解决,比如在 PreCommit
阶段,当一个参与者收到了请求之后其他参与者和协调者挂了或者出现了网络分区,这个时候收到消息的参与者都会进行事务提交,这就会出现数据不一致性问题。
所以,要解决一致性问题还需要靠 Paxos
算法⭐️ ⭐️ ⭐️ 。
3.Paxos
算法
Paxos
算法是基于消息传递且具有高度容错特性的一致性算法,是目前公认的解决分布式一致性问题最有效的算法之一,其解决的问题就是在分布式系统中如何就某个值(决议)达成一致 。
在 Paxos
中主要有三个角色,分别为 Proposer提案者
、Acceptor表决者
、Learner学习者
。Paxos
算法和 2PC
一样,也有两个阶段,分别为 Prepare
和 accept
阶段。
3.1. prepare 阶段
Proposer提案者
:负责提出proposal
,每个提案者在提出提案时都会首先获取到一个 具有全局唯一性的、递增的提案编号N,即在整个集群中是唯一的编号 N,然后将该编号赋予其要提出的提案,在第一阶段是只将提案编号发送给所有的表决者。Acceptor表决者
:每个表决者在accept
某提案后,会将该提案编号N记录在本地,这样每个表决者中保存的已经被 accept 的提案中会存在一个编号最大的提案,其编号假设为maxN
。每个表决者仅会accept
编号大于自己本地maxN
的提案,在批准提案时表决者会将以前接受过的最大编号的提案作为响应反馈给Proposer
。
下面是
prepare
阶段的流程图,你可以对照着参考一下。
3.2. accept 阶段
当一个提案被 Proposer
提出后,如果 Proposer
收到了超过半数的 Acceptor
的批准(Proposer
本身同意),那么此时 Proposer
会给所有的 Acceptor
发送真正的提案(你可以理解为第一阶段为试探),这个时候 Proposer
就会发送提案的内容和提案编号。
表决者收到提案请求后会再次比较本身已经批准过的最大提案编号和该提案编号,如果该提案编号 大于等于 已经批准过的最大提案编号,那么就 accept
该提案(此时执行提案内容但不提交),随后将情况返回给 Proposer
。如果不满足则不回应或者返回 NO 。
当 Proposer
收到超过半数的 accept
,那么它这个时候会向所有的 acceptor
发送提案的提交请求。需要注意的是,因为上述仅仅是超过半数的 acceptor
批准执行了该提案内容,其他没有批准的并没有执行该提案内容,所以这个时候需要向未批准的 acceptor
发送提案内容和提案编号并让它无条件执行和提交,而对于前面已经批准过该提案的 acceptor
来说 仅仅需要发送该提案的编号 ,让 acceptor
执行提交就行了。
而如果 Proposer
如果没有收到超过半数的 accept
那么它将会将 递增 该 Proposal
的编号,然后 重新进入 Prepare
阶段 。
对于
Learner
来说如何去学习Acceptor
批准的提案内容,这有很多方式,读者可以自己去了解一下,这里不做过多解释。
3.3. paxos
算法的死循环问题
其实就有点类似于两个人吵架,小明说我是对的,小红说我才是对的,两个人据理力争的谁也不让谁🤬🤬。
比如说,此时提案者 P1 提出一个方案 M1,完成了 Prepare
阶段的工作,这个时候 acceptor
则批准了 M1,但是此时提案者 P2 同时也提出了一个方案 M2,它也完成了 Prepare
阶段的工作。然后 P1 的方案已经不能在第二阶段被批准了(因为 acceptor
已经批准了比 M1 更大的 M2),所以 P1 自增方案变为 M3 重新进入 Prepare
阶段,然后 acceptor
,又批准了新的 M3 方案,它又不能批准 M2 了,这个时候 M2 又自增进入 Prepare
阶段。。。
就这样无休无止的永远提案下去,这就是 paxos
算法的死循环问题。
那么如何解决呢?很简单,人多了容易吵架,我现在 就允许一个能提案 就行了。