目录
Paxos一致性算法详解
1.问题描述
假设有一组可以提出提案的进程集合,那么对于一个一致性算法来说需要保证以下几点:
- 在这些被提出的提案中,只有一个会被选定。
- 如果没有提案被提出,那么就不会有被选定的提案。
- 当一个提案被选定后,进程应该可以获取被选定的提案信息。
而且为了满足安全一致性,有如下要求:
- 要求只有被提出的提案才能被选定。
- 只能有一个提案被选定。
- 如果某个进程认为某个提案被选定了,那么这个提案必须是真的被选定的那个。
在Paxos一致性算法中,有Proposer、Acceptor和Learner三种参与角色,假设不同参与者之间可以通过收发消息来进行通信。其中Proposer提出提案,Acceptor决定哪个提案作为被选定的提案,Learner作为提案的接受者。
2.推导过程
本部分描述性文字比较多,如果对此不感兴趣,可以直接看第3部分的算法流程。
首先,我们希望即使只有一个提案被提出,仍然可以有提案被选定。这暗示我们:
P1:一个Acceptor必须批准它收到的第一个提案。
但根据P1,就可能导致多个提案分别被数量一致或相近的Acceptor批准,比如A、B分别被1、2批准,这样仍然无法确定唯一一个提案。怎么解决呢?
P2:一个提案被选定必须得到半数以上的Acceptor的批准。
这一需求的引入,表明Paxos算法要求奇数个节点组成集群,它的容错能力为2F+1,即最多允许F个节点同时出现故障。
至此我们已经保证了,只要有提案被提出,就一定会有一个提案被选定。并且结合P1和P2,暗示一个Acceptor必须能够批准多个提案。我们在这里引入一个全局有序的编号,ProposalID来唯一标识每一个被Acceptor批准的提案。
当一个提案——我们用value指代它的内容,被半数以上Acceptor批准后,我们就认为该value被选定了,该提案也被选定了,此时提案变成了一个由编号和Value组成的结合体:[ProposalID,Value]。
但此时又出现了一个问题,Acceptor可以批准对个提案,这就导致可能会有多个不同提案被选定,这违背了第1节中只有一个提案被选定的要求。所以:
P3:如果提案[ProposalID0,Value0]被选定了,那么后续所有被选定的提案的Value都必须为Value0。
只要能满足P3,即使有多个提案被选定,我们也可以保证所有被选定的提案具有相同的value值。
那怎么满足P3的要求呢?我们可以进一步得到
P3-1:如果提案[ProposalID0,Value0]被选定了,那么后续所有Acceptor批准的提案的Value都必须为Value0。
只要满足P3-1,P3肯定满足,但问题来了。如果此时提案[ProposalID0,Value0]被选定了,但是AcceptorN正好没有批准这个提案因为通信是异步的,即它不知道该提案已经被选定,它就没法对它后续批准的提案进行设置,这仍然会使得P3不满足。所以我们可以进一步对Proposer要求:
P3-2:如果提案[ProposalID0,Value0]被选定了,那么之后任何Proposer提出的提案Value值都必须为Value0。
综上,我们可以得到Propser的执行流程:
1.Proposer向半数以上Acceptors发送一个只含有ProposalID的消息,该请求称为准备请求。
2.如果Proposer收到超过一半Accepotor的回复,就使用回复中最大的ProposalID的value,加上准备消息的ProposalID作为提案,如果回复中value为空,则可以提出任意值。
3.向回复的Acceptors发送Accept请求,请求它们批准提出的Proposal。
Acceptor的执行流程如下:
1.当收到准备请求时,如果ProposalID大于之前收到的准备消息,则回复其批准过的最大编号的提案。
2.当收到Accept请求时,如果Acceptor没有回复过一个更大ProposalID的准备消息,接受该Proposal并回复。
3.算法流程
我们可以得到Paxos的算法流程:
1.Proposer选择一个提案编号Mn(为保证唯一,可以使用时间戳+ServerID),然后向半数以上的Acceptor发送编号为Mn的准备请求。
2.如果一个Acceptor,A收到一个编号为Mn的准备请求,且Mn>A之前响应过的准备消息的最大编号(maxM_pre),则A将其批准过的最大编号的提案(既包含编号又包含value,假定为[Ma_max,va])作为响应反馈给Proposer,并且令maxM_pre=Mn(即不再回复编号小于等于Mn的准备消息)。
3.如果Proposer收到来自半数以上的Acceptor对于准备消息Mn返回的反馈,那么他就会发送一个提案[Mn,Vn]给Acceptors,其中Vn为收到的反馈中编号最大的提案的值(即可能为va),如果响应中不包含任何提案,则可以是任意值。这一步被称为Accept请求。
4.如果Acceptor收到这个针对[Mn,Vn]提案的Accept的请求,只要maxM_pre<=Mn,即没响应过更大编号的准备请求,则通过这个提案。同时,令Ma_max=Mn,va=Vn。
伪代码如下:
通过伪代码我们可以发现一个问题,假设有两个ProposerA和B,A发送一个准备得到了半数以上的反馈,正在进行accept请求时,B也发送了一个准备,此时A检测到有更新的提案,会重新回到步骤1,重复上面过程,将会形成一个活锁(Livelock)的情形。
可以考虑选择一个Proposer作为主Proposer,规定只有它能提出提案,这样就能保证算法流程的活性。
4.Multi-Paxos算法
原始的Paxos算法(Basic Paxos)只能对一个值形成决议,决议的形成至少需要两次网络来回,在高并发情况下可能需要更多的网络来回,极端情况下甚至可能形成活锁。如果想连续确定多个值,原始Paxos算法就无法解决。
实际应用中几乎都需要连续确定多个值,而且希望能有更高的效率。Multi-Paxos正是为解决此问题而提出。Multi-Paxos基于原始Paxos做了两点改进:
- 针对每一个要确定的值,运行一次Paxos算法实例(Instance),形成决议。每一个Paxos实例使用唯一的Instance ID标识。
- 在所有Proposers中选举一个Leader,由Leader唯一地提交Proposal给Acceptors进行表决。这样没有Proposer竞争,解决活锁问题。在系统中仅有一个Leader进行Value提交的情况下,Prepare阶段就可以跳过,从而将两阶段变为一阶段,提高效率。
Multi-Paxos流程:
Multi-Paxos首先需要选举Leader,Leader的确定也是一次决议的形成,所以可执行一次Basic Paxos实例来选举出一个Leader。选出Leader之后只能由Leader提交Proposal,在Leader宕机之后服务临时不可用,需要重新选举Leader继续服务。在系统中仅有一个Leader进行Proposal提交的情况下,Prepare阶段可以跳过。
Multi-Paxos通过改变Prepare阶段的作用范围至后面Leader提交的所有实例,从而使得Leader的连续提交只需要执行一次Prepare阶段,后续只需要执行Accept阶段,将两阶段变为一阶段,提高了效率。为了区分连续提交的多个实例,每个实例使用一个Instance ID标识,Instance ID由Leader本地递增生成即可。
Multi-Paxos允许有多个自认为是Leader的节点并发提交Proposal而不影响其安全性,这样的场景即退化为Basic Paxos。
Chubby和Boxwood均使用Multi-Paxos。ZooKeeper使用的Zab也是Multi-Paxos的变形。
5.参考文献
[1]https://zhuanlan.zhihu.com/p/31780743
[2]《从Paxos到Zookeeper分布式一致性原理与实践》