理解paxos协议-分布式共识算法(consensus)

理解paxos协议


  本文的目的是一步步讲解paxos如何是如何推断和完备的。首先要了解,分布式一致性(consistency)和分布式共识(consensus)并非一个东西来的,然而网上大部分的人都直接把分布式共识翻译为分布式一致性,致使像paxos,raft这样的算法被误认一致性算法,并拿来跟2pc,3pc作比较,这是不对的。以前我也一直存在疑惑,有次吃饭的时候跟同导师的一个小哥聊天的时候,他一语道破,虽然不属于官方的解释,可是理解起来却格外的清晰:算法

一致性是要求全部的节点一致,共识算法是只要大部分节点认可安全

  仍是得说明,这个并非官方解释,在笔者找到更合适的说法前,暂时先用这个代替。分布式共识算法要解决的问题是什么,在分布式的状况下,咱们常常会把数据分布到不一样的节点上,如何来保证这些数据尽量达成一致就是paxos要解决的问题了。在论文的一开始,做者就讲明了分布式共识算法要达成的目标。(ps:若是存在理解不正确的地方,欢迎指出,谢谢)less

  • 达成一致的结果必定是由某个进程或者某个应用提出来的,而不是实现约定的结果。
  • 最后要达成一致代表只有一个值能被选中
  • 第三点是指,只有已经被选中的值才能被其余不参与决策的人知道,属于learner的功能,很少细讲。

  为了简单的来说解整个过程,我决定把文中说起的三种以为拟人化。首先负责提议的称为<font color="#0f88eb">议员(proposal)</font>,负责决策的是<font color="#0f88eb" >选民(acceptor)</font>,负责学习的是<font color="#0f88eb" >群众(learner)</font>。有几个约束,各个角色之间互相通讯是利用异步消息队列,可能存在不可达甚至宕机重启的现象,可是没有拜占庭错误(也就是有偷懒睡觉的,可是没有叛徒,不会修改消息内容)。且咱们规定提案只有通过大多数人赞成才能肯定下来(也就是超过半数的人)。首先来看第一条约束异步

  • P1:选民必须接受他收到的第一个提案(An acceptor must accept the first proposal that it receives).

  为何有这么一条呢?为了达成一致,直观的想法都是,若是接收到了一个消息我就选择accept这个消息,由于我没有合适的拒绝策略。就相似于,每次议员提出一个提案,底下的全部选民就都赞成了,除了那些打瞌睡的不知道这件事的除外。之因此有这一条约束,我以为最主要的缘由是目前咱们没法给出一个合适的拒绝策略。可是这个选择策略很明显也是有问题的,若是此时有多个议员同时提出提案,而后底下的选民们分别接受到了不一样的提案并回复了,可是因为选民分散致使没有一个提案能被大多数的表明选中,这样就致使了选举失败(存在活锁现象)。所谓活锁就是一直在提议却一直没法选择出最终结果。那么若是要知足P1的约束,且要求必定要有大多数的选民都赞成一个提案,则催生了另外一个条件,选民能够选择多个提案。好比A,B两个议员同时提出了两个提案,做为选民k的你,能够在赞成A的提案以后,又赞成B的提案。不过这样你确定又会问,新的选择策略就是全选吗?确定不行的,因而做者提出,在提案的基础上,咱们加入一个全局的编号,这个编号全局有序,谁编号大就选谁,这样最公平(至于提案如何生成这个编号,不在论文的讨论范围,可是我还要啰嗦句,具体实现中,这个编号的生成是很巧妙,不是随意递增就能够的,并且这个编号是后文判断的基础,很是重要)。瞎逼逼了这么多,就是为了说明一点,咱们没有合适的拒绝策略/选择策略因此选择第一个达到的提案,可是为了保证必定会有值能选出,则须要能够选民具备选择多个提案的能力(不少状况下并无一个绝对有效的拒绝策略,那么换种思路就是多样化的选择策略,可是最终只有一个能成功)。
  那么咱们如何保证一致性呢?来看看另外一个新的约束分布式

  • P2:若是一个提案(with value=v,number=n)被选中,那么后续编号大于n的全部被选中的提案必须包含value=v。(If a proposal with value v is chosen, then every higher-numbered proposal that is chosen has value v.)

  P2实际上是个很强的约束,若是咱们能保证在一次paxos选举中,若是已经有一个能被大多数acceptor接受的结果,那么咱们就应该坚决不移的坚持这个结果,天然能得到最终一致性。从前文来看,选举中即便再次发生新提案,若是要经过仍是须要acceptor的接受的。那么P2的变种说法就是:学习

  • P2a:(If a proposal with value v is chosen, then every higher-numbered proposal accepted by any acceptor has value v.)

  P2a和P2的区别就是在提案那里加了一个定语,因此其实两种说法是相同的,由于提案要想被接受,必然须要有大多数的acceptor去接受这个提案。可是存在一种场景,假设一个proposer宕机了,而后重启后,发布了一个提案(高编号,可是值不一样),此时恰好存在一些好死不死的acceptor,也是没有收到任何的提案,而后这两个一拍即合的就选择了这个新提案(根据P1)。可是根据P2,这些acceptor不该该接受这样的提案,也就冲突了。其实就是缺乏对于proposer的限制。因此须要加强下P2,对实现对proposer的限制(也避免P1和P2之间的冲突):优化

  • P2b:If a proposal with value v is chosen, then every higher-numbered proposal issued by any proposer has value v.

  跟P2a很类似,也是针对提案加了定语,可是这里的issue倒是一个很是重要的过程,论文后面才会讲到,先按下不表。先来讲说,这个跟上一个有什么不一样。首先,issue是发生在accept前面的,accept的提案必定是issue过了,可是issue的提案就不必定能被accept了(issue的提案只是在第一阶段被选中,可是为了最终一致性可能会舍弃)。P2b->P2a是没有问题的,并且看起来P2b的限制更强了。那么提案在issue的时候,就必需要求带有已经被选中的value这一点,使得压力不用放在acceptor那里(由于这里发送出去的时候,就代表是个已经被大多数acceptor接受的结果,acceptor只要对比本地的结果就能够最终确认了)。
  那么如何去保证P2b呢,只要在一次paxos提案中,若是已经有一个value被大多数的acceptor接受,那么后续更高编号的proposal就必须带有value = v。这个要求每一个在提出新的proposal的proposer,都要收集本次提案中,是否已经有被大多数acceptor接受的提案,有的话,就作个顺水人情,帮他完成掉这个提案。那么就催生了一个新的条件:编码

  • P3c:For any v and n, if a proposal with value v and number n is issued, then there is a set S consisting of a majority of acceptors such that either (a) no acceptor in S has accepted any proposal numbered less than n, or (b) v is the value of the highest-numbered proposal among all proposals numbered less than n accepted by the acceptors in S.

  简单来讲,那就是,之因此有大多数集的acceptor接受了这个提案,这些acceptor的组成是,(a).历来没有接受过比n小的提案,这样都会接受,就说明这是P1致使的结果,由于历来没有接受过提案,那么n做为第一个到来的提案,天然能够接受了。(b).v是编号小于n的提案里面,被接受的编号最大的那个提案的值,这是为了知足P2,作个顺水人情嘛。到这里,基本整个算法的流程就算是差很少了。可是到这里,读者可能会疑问,好像不知道编号有什么大的做用的,虽说选大的编号,可是仅仅是上述的说法,不带编号好像能够,来看看算法的基本流程就明白了。.net

    • prepare阶段:一个proposer选择一个新的编号,而后发送一个prepare请求给全部的acceptors,要求他们回应
    1. 承诺再也不接受编号小于n的提案。
    2. 若是有存在已经接受的提案,请把提案和编号发给我翻译

      • accept阶段:若是一个proposer接收到了大多数acceptors的回应说能够。那么proposer就要issue这个提案(with number n,value v),这里v的选择就根据P2c来,若是prepare阶段没有返回任何的已经接受的提案的话,那么就有proposer随机选择。若是有返回,那就在里面选择编号最大的一个的提案中的值来做为v。而后将这个提案封装后发送给acceptors

      这个就是算法的过程,仔细来看,每一个proposer能够发起两次请求,而每一个acceptors能够接收两次请求,作出两次回应。首先对于acceptors来讲,acceptor首先能够无视任何一次请求(也就是超时或者宕机)都不会形成任何安全性的影响。那么在响应的时候呢,acceptor能够响应全部的prepare请求(无论是拒绝也好,接受也好)。可是对于accept请求的话,若是已经承诺过不接受的话,那么就不能相应本身要接受这个请求。也就是说:

    • P1a:An acceptor can accept a proposal numbered n iff it has not responded to a prepare request having a number greater than n.

      一个acceptor只有在没有接受过其余编号大于n的prepare请求的状况下,才能接受一个编号为n的请求。这个约束是能够推倒出P1的,由于,若是他没有接受过任何请求,那么就能够接受任何请求了。到这里,P1,P2两个约束实际上是相互扶持的P1决定第一次该怎么作,P2决定后续该怎么作。
      算法到这里就算是所有结束了,可是这只是理论上的实现,实际编码则须要作相应的优化,好比编号的产生,好比acceptor在相应的时候反馈一些有用的信息使得proposer能中止本身过期的操做等。可是,这个算法是存在活锁的问题的。假如如今有两个proposers,A,B,A先发起了prepare阶段并得到了大多数的支持,而后紧接着B带着更高编号的来了知道A已经得到支持,可是因为B编号更大能够得到大多数的支持。紧接着A进入accept阶段,发现,你们都接受了更高编号,尴尬了,因而立刻发起新一轮的prepare阶段,换了更大编号的。一样B在进入accept阶段的时候也发现了这个问题,因而两个proposers相互更新编号,即便协议已经达成一致却一直没法更新。这就是活锁问题,论文后续提出,为了解决活锁问题,最好引入一个leader proposer,由这个leader来发起提议。可是leader选举自己也是一个paxos问题。

    内容参考:Paxos Made Simple,paxos算法 - 维基百科csdn的一篇博客

    相关文章
    相关标签/搜索