Paxos协议核心流程讲解
Posted 会打码的羊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Paxos协议核心流程讲解相关的知识,希望对你有一定的参考价值。
大家好,羊哥在这里向大家分享一个非常知名而又艰深难懂的分布式共识协议——Paxos协议。作为一个合格的后台开发工程师,如果能够熟悉Paxos协议,并在理解的基础之上掌握开发分布式系统的能力,无疑在能力上又提高了一个很大的level。当然,就算是毫不相关的人,掌握Paxos协议也能提升自己对分布式系统的理解,特别是还能够学习到一种基于数学推理来设计特定领域协议的能力,总之,百利而无一害。
然而,Paxos协议是真的难懂,羊哥本人看过Paxos协议作者Lamport的两篇论文:《The Part-Time Parliament》与《Paxos Made Simple》的英文版和译文版,无法理解;知乎上看过数十个关于Paxos的回答,依旧枉然;Google了相关的无数博文,仍然无效。直到最近,接到了关于分布式协议的分享任务,分析了phxpaxos的源码(https://github.com/Tencent/phxpaxos),与组内大佬细致探讨才终于搞懂。由此可以想见,只有极少数工程师能从根本上理解Paxos协议,所以学成归来的羊哥决定化身一位布道师,用几篇文章向大家分析讲解一下Paxos协议,本文是这一系列文章的第一篇,主要围绕Paxos协议相关的流程步骤等进行阐述,协议推理部分放在第二篇来讲。
在Paxos协议中,每台主机需要同时扮演3种角色,分别是Proposer,Acceptor和Learner。其中Proposer用于直接处理对外的请求,并向整个系统发出提案,提议要被选择的值;Acceptor负责处理Proposer提出的提议,根据特定的规则判断是否能够接受提案,保存选定的值,在和Proposer在交互中达成系统整体的共识;Learner本身不参与共识的决策,只是与Acceptor配合记录与学习被选定的值,并根据习得的值触发状态机的状态迁移。需要注意的是,上述3种角色的划分并非重点,我们只要建立模糊的印象即可。因为每台主机都扮演了上述所有的角色,因此这种角色的设定也是满足基本的平等性要求的。
为了区分不同的提案,Paxos协议要求系统中的所有提案都有其唯一编号,并且编号的大小是趋势递增的,如果是同一个Proposer提交的提案,我们可以简单的通过提案的编号的大小判断提案提交的先后顺序。对于同时到来的两个提案,Acceptor更偏好编号更大的提案。这里也并非重点,只是Paxos协议中的一个很小的要求,并且也很容易实现。例如,我们可以将提案编号按照bit前后拆分为选举周期和服务器ID两个部分,其中服务器ID是和具体的主机绑定的,选举周期是由每台主机各自维护的,从0开始,依次递增,通过自行序列化存储保证不会重复使用。每次Proposer提交提案的时候,都会在选举周期加上1,更新提案编号propose_id之后再发起提议。
1.提案阶段
对于分布式系统的多机一致性,最基础的问题是针对一个特定的值在多台主机之间达成共识,这也是Basic Paxos算法所要解决的问题,下面介绍一下Basic Paxos算法中为了达成共识需要执行的步骤。Proposer收到请求之后,首先进入提案阶段,选取提议编号propose_id后会向系统中的所有Acceptor广播提案。Acceptor可能同时接受多个Proposer提交的提案,这些接受过的提案的值都相同,只是提案编号不同(至于具体为什么这样设置,会在第二篇中详细推理证明)。每个Acceptor各自维护minProposeId、acceptedProposal,分别表示他所收到的编号最大的提案的编号,以及它曾经接受过的提案。初始minProposeId设为无穷小,acceptedProposal设为None,这表示Acceptor没有收到过任何提案。假设Proposer提交的提案编号propose_id为n,大于等于minProposeId,Acceptor首先将minProposeId更新为对应n,再用(minProposeId,acceptedProposal)回复给Proposer。Acceptor的这种回复可以视为一种承诺,Acceptor不会再接受任何提案编号小于n的提案。相反,如果n小于minProposeId,这违反了Acceptor之前作出的承诺,Acceptor会拒绝这样的提案,直接用minProposeId回复给proposer。Proposer收到minProposeId之后,发现比自己提交的提案编号大,可以直接判断Acceptor拒绝了自己的提案。需要注意的是,在提案阶段,Proposer收到Acceptor同意提议的回复时,若回复内容中acceptedProposal为None则表示Acceptor之前没有接受过任何值,这种情况下,Proposer可以顺利发布自己提议的值;反之则表示Acceptor曾经接受过提案,此时,Proposer只能被动接受已经被Acceptor接受的值,以免影响系统整体的一致性。
2. 批准阶段
Proposer如果在提案阶段,没有收到系统中绝大部分Acceptor同意接受提案的回复,就会重新回到阶段一,尝试使用新的提案编号再次发起提案,否则就会进入批准阶段,将提案提交给Acceptor进行批准。
在批准阶段,如果Proposer收到的所有回复中,acceptedProposal均为None,这种情况下,Proposer无需修改提案。否则,Proposer需要将提案中的值修改为收到的所有回复的acceptedProposal中,编号最大的acceptedProposal中的值,并向所有Acceptor发起批准请求。Proposer的上述行为可以理解为将最近已经达成的共识再向系统广播一次,将已经固定的结论进行进一步强化。Acceptor收到批准请求之后,如果批准请求中的提案编号不小于minProposeId,就接受这个提案,更新acceptedProposal、minProposeId。Acceptor无论成功与否都直接将minProposeId作为返回值返回给Proposer,Proposer可以根据返回的minProposeId是否与提案的编号相等判断是否成功批准提案。对于Proposer来说,如果大部分Acceptor回复批准提案,表示提交成功,否则提交失败。
分布式系统中的每个主机可以理解为一个以一定顺序执行客户端发起的命令的状态机,这个状态机在输入命令之后,会触发状态流转,迁移到新的状态,并产生相应的输出。如果每台主机上面的状态机是完全一致的,输入的命令的顺序和内容也完全一致,那么就能保证系统中的所有主机的最终状态也是完全相同的。在实际应用的系统中,只在一个值上达成共识是远远不够的,我们需要在一个命令序列上的所有值都达成共识,才能推动分布式系统持续运转,所以,我们又在Basic Paxos的基础之上,引入了Multi Paxos。
在Multi Paxos中,上述每一个命令确定的过程称作一个instance,都通过运行独立的Basic Paxos逻辑来达成共识。每个instance都有编号,设为instanceId,此编号与命令的执行顺序一致,从1开始,顺序递增。Learner从1开始,依次顺序执行对应instanceId确定的命令,推动分布式系统不断运行。Proposer在为新的命令发起提议的时候,会指定instanceId,如果指定instanceId的命令已经被写入,会继续尝试在instanceId更大的instance中运行Basic Paxos算法,完成对应命令的写入。
以上就是关于Paxos协议核心流程的讲解,我们参考上述流程步骤进行实现,就能在分布式系统上达成一个命令序列的共识,进而推动状态机发生状态迁移,使得分布式系统能够持续可靠运转。上述流程虽然清晰,但是我们很难直接在原理层面理解流程设计的原由,难免质疑其正确性,很容易因为错误的理解而做出一个蹩脚的实现。如果你有兴趣探明Paxos协议流程设计背后的数学思考,敬请关注羊哥关于Paxos协议的后续文章。当然,羊哥所述可能也有疏漏,如果你有什么问题或者质疑,欢迎在留言区写下你的思考,谢谢~
以上是关于Paxos协议核心流程讲解的主要内容,如果未能解决你的问题,请参考以下文章