paxos算法简析

序言

本文是基于paxos made simple论文来对paxos进行一些简要解析,掺杂了个人对paxos论文的理解,不保证解析的正确性。

1 一致性

paxos提供的是达成分布式下状态一致性共识的一种算法思想,对于一致性来说,要满足两个方面的需求:

  • 安全性(Safety):指那些需要保证永远都不会发生的事情。

1.只有被提出的提案才能被选定。
2.只能有一个值被选定。
如果某个进程认为某个提案被选定了,那么这个提案必须是真的被选定的那个。

  • 活性(Liveness):是指那些最终一定会发生的事情。论文中没有对该内容进行精确的描述,本文中也暂不进行讨论。

2 paxos算法中的角色

paxos算法使用的是非拜占庭模式中的异步模型,有三种角色,具体的实现中,一个进程可能充当不止一种角色:

  • proposers:提案者
  • acceptors:接受者
  • learners:学习者

3 提案的选定(共识过程)

对于单proposer或单acceptor的模式,比较简单,容易出现单点问题,这里不进行描述。
paxos不要求全部的acceptor达成共识,只要acceptor中的多数(一半以上)达成共识,就认为提案被选定。

3.1 paxos算法思想的推导过程

在没有失败和消息丢失的情况下,如果我们希望即使在只有一个提案被提出的情况下,仍然可以选出一个提案来。

P1:一个Acceptor必须通过(accept)它收到的第一个提案。

但这会引起多提案时无法确定提案的问题,acceptor通过了不同的提案。这意味着acceptor必须能够通过(accept)不止一个提案。这也就意味着acceptor需要记录处理过的提案,通过一种实现机制,为每个提案设定一个编号来避免提案的混淆。当一个具有某value值的提案被半数以上的Acceptor通过后,我们就认为该value被选定了。
允许多个提案被选定,必须保证所有被选定的提案具有相同的value值。在提案编号上规约,需要保证:

P2 . 如果具有value值v的提案被选定(chosen)了,那么所有比它编号更高的被选定的提案的value值也必须是 v 。

被选定的提案,首先必须被至少一个Acceptor通过,我们可以通过满足如下条件来满足 P2 :

P2 a. 如果具有value值 v 的提案被选定(chosen)了,那么所有比它编号更高的被Acceptor通过的提案的value值也必须是 v。

因为通信是异步的,一个提案可能会在某个Acceptor c 还未收到任何提案时就被选定了。假设一个新的Proposer苏醒了,然后产生了一个具有另一个value值的更高编号的提案,根据 P1 ,就需要 c 通过这个提案,但是这与 P2 a 矛盾。因此如果要同时满足 P1 和 P2 a ,需要对 P2a 进行强化:

P2 b. 如果具有value值v的提案被选定,那么所有比它编号更高的被Proposer提出的提案的value值也必须是 v 。

为了发现如何保证 P2 b ,我们来看看如何证明它成立。我们假设某个具有编号m和value值v的提案被选定了,需要证明具有编号 n(n > m) 的提案都具有value值 v 。我们可以通过对 n 使用归纳法来简化证明,这样我们就可以在额外的假设下——即编号在 m…(n-1) 之间的提案具有value值 v ,来证明编号为n的提案具有value值 v 。因为编号为m的提案已经被选定了,这意味着肯定存在一个由半数以上的Acceptor组成的集合 C , C 中的每个Acceptor都通过了这个提案。再结合归纳假设, m 被选定意味着:

C 中的每个Acceptor都通过了一个编号在 m…n-1 之间的提案,每个编号在 m…(n-1) 之间的被Acceptor通过的提案都具有value值 v 。

因为任何包含半数以上的Acceptor的集合S都至少包含 C 中的一个成员,我们可以通过维护如下不变性就可以保证编号为n的提案具有value v :

P2 c. 对于任意的 n 和 v ,如果编号为 n 和value值为 v 的提案被提出,那么肯定存在一个由半数以上的Acceptor组成的集合 S ,可以满足条件 a 或者 b 中的一个:
a.S 中不存在任何的Acceptor通过过编号小于 n 的提案
b.v 是 S 中所有Acceptor通过的编号小于 n 的具有最大编号的提案的value值。

为了维护 P2 c 的不变性,一个Proposer在产生编号为 n 的提案时,必须要知道某一个将要或已经被半数以上Acceptor通过的编号小于 n 的最大编号的提案。获取那些已经被通过的提案很简单,但是预测未来会被通过的那些却很困难。在这里,为了避免让Proposer去预测未来,我们通过限定不会有那样的通过情况来控制它。换句话说,Proposer会请求Acceptors不要再通过任何编号小于 n 的提案。这就导致了如下的提案生成算法:

  • Proposer选择一个新的提案编号 n ,然后向某个Acceptors集合的成员发送请求,要求Acceptor做出如下回应:

1.保证不再通过任何编号小于 n 的提案
2.当前它已经通过的编号小于 n 的最大编号的提案,如果存在的话。
3.我们把这样的一个请求称为编号为 n 的prepare请求。

  • 如果Proposer收到了来自半数以上的Acceptor的响应结果,那么它就可以产生编号为 n ,value值为 v 的提案,这里 v 是所有响应中编号最大的提案的value值,如果响应中不包含任何的提案那么这个值就可以由Proposer任意选择。

Proposer通过向某个Acceptors集合发送需要被通过的提案请求来产生一个提案(此时的Acceptors集合不一定是响应前一请求的那个Acceptors集合)。我们称此请求为 accept 请求。
目前描述了proposer端的算法,对于Acceptor端,它可能会收到来自Proposer端的两种请求:prepare请求和accept请求。Acceptor可以忽略任何请求而不用担心破坏其算法的安全性。因此我们只需要说明它在什么情况下可以对一个请求做出响应。它可以在任何时候响应一个prepare请求,对于一个accept请求,只要在它未违反现有承诺的情况下才能响应一个accept请求(通过对应的提案)。换句话说:

P1 a. 一个Acceptor可以接受一个编号为 n 的提案,只要它还未响应任何编号大于 n 的prepare请求。

对于编号为n的prepare请求,如果acceptor已经对编号大于n的prepare请求做出了响应,它没必要对这个请求做出响应,我们会让Acceptor忽略这样的prepare请求。我们也会让它忽略那些它已经通过的提案的prepare请求。
通过这个优化,Acceptor只需要记住它已经通过的最大编号的提案以及它已经做出prepare请求响应的最大编号的提案的编号。因为必须要保证 P2 c 的不变性即使在出错的情况下,Acceptor必须记住这些信息即使是在出错或者重启的情况下。Proposer可以总是可以丢弃提案以及它所有的信息—只要它可以保证不会产生具有相同编号的提案即可。
将Proposer和Acceptor放在一块,我们可以得到算法的如下两阶段执行过程:

  • Phase1:

1.Proposer选择一个提案编号 n ,然后向Acceptors的某个majority集合的成员发送编号为 n 的prepare请求。
2.如果一个Acceptor收到一个编号为 n 的prepare请求,且 n 大于它已经响应的所有prepare请求的编号,那么它就会保证不会再通过(accept)任何编号小于 n 的提案,同时将它已经通过的最大编号的提案(如果存在的话)作为响应。

  • Phase2:

1.如果Proposer收到来自半数以上的Acceptor对于它的prepare请求(编号为 n )的响应,那么它就会发送一个针对编号为 n ,value值为 v 的提案的accept请求给Acceptors,在这里 v 是收到的响应中编号最大的提案的值,如果响应中不包含提案,那么它就是任意值。
2.如果Acceptor收到一个针对编号 n 的提案的accept请求,只要它还未对编号大于 n 的prepare请求作出响应,它就可以通过这个提案。

一个Proposer可能或产生多个提案,只要它是遵循如上的算法即可。它可以在任意时刻丢弃某个提案。(即使针对该提案的请求和响应在提案被丢弃很久后才到达,正确性依然可以保证)。如果某个Proposer已经在试图生成编号更大的提案,那么丢弃未尝不是一个好的选择。因此如果一个Acceptor因为已经收到更大编号的prepare请求而忽略某个prepare或者accept请求时,那么它也应当通知它的Proposer,然后该Proposer应该丢弃该提案。当然,这只是一个不影响正确性的性能优化。

4 获取被选定的提案值

为了获取被选定的值,一个Learner必须确定一个提案已经被半数以上的Acceptor通过。最明显的算法是,让每个Acceptor,只要它通过了一个提案,就通知所有的Learners,将它通过的提案告知它们。这可以让Learners尽快的找出被选定的值,但是它需要每个Acceptor都要与每个Learner通信—所需通信的次数等于二者个数的乘积。

在假定非拜占庭错误的情况下,一个Learner可以很容易地通过另一个Learner了解到一个值已经被选定。我们可以让所有的Acceptor将它们的通过信息发送给一个特定的Learner,当一个value被选定时,再由它通知其他的Learners。这种方法,需要多一个步骤才能通知到所有的Learners。而且也是不可靠的,因为那个特定的Learner可能会失败。但是这种情况下的通信次数,只是Acceptors和Learners的个数的和。

更一般的,Acceptors可以将它们的通过信息发送给一个特定的Learners集合,它们中的每个都可以在一个value被选定后通知所有的Learners。这个集合中的Learners个数越多,可靠性就越好,但是通信复杂度就越高。

由于消息的丢失,一个value被选定后,可能没有Learners会发现。Learner可以询问Acceptors它们通过了哪些提案,但是一个Acceptor出错,都有可能导致无法判断出是否已经有半数以上的Acceptors通过的提案。在这种情况下,只有当一个新的提案被选定时,Learners才能发现被选定的value。因此如果一个Learner想知道是否已经选定一个value,它可以让Proposer利用上面的算法产生一个提案。

5 进展性

活锁:两个Proposers持续地生成编号递增的一系列提案,但是没有提案会被选定。Proposer p 为一个编号为 n 1 的提案完成了Phase1,然后另一个Proposer q 为编号为 n 2 (n 2 > n 1 ) 的提案完成了Phase1。Proposer p 的针对编号 n 1 的提案的Phase2的所有accept请求被忽略,因为Acceptors已经承诺不再通过任何编号小于 n 2 的提案。这样Proposer p 就会用一个新的编号 n 3 (n 3 > n 2 ) 重新开始并完成Phase1,这又会导致在Phase2里Proposer q 的所有accept请求被忽略,如此循环往复。
活性(liveness):为了保证进度,必须选择一个特定的Proposer来作为一个唯一的提案提出者。如果这个Proposer可以同半数以上的Acceptors通信,同时可以使用一个比现有的编号都大的编号的提案的话,那么它就可以成功的产生一个可以被通过的提案。再通过当它知道某些更高编号的请求时,舍弃当前的提案并重做,这个Proposer最终一定会产生一个足够大的提案编号。如果系统中有足够的组件(Proposer,Acceptors及通信网络)工作良好,通过选择一个特定的Proposer,活性就可以达到。著名的FLP结论 [1] 指出,一个可靠的Proposer选举算法要么利用随机性要么利用实时性来实现—比如使用超时机制。然而,无论选举是否成功,安全性都可以保证。

6 实现

Paxos算法 [5] 假设了一组进程网络。在它的一致性算法中,每个进程扮演着Proposer,Acceptor及Learner的角色,该算法选定一个Leader来扮演那个特定的Proposer和Learner。Paxos一致性算法就是上面描述的那个,请求和响应都是以普通消息的方式发送(响应消息通过对应的提案的编号来标识以防止混淆)。使用可靠性的存储设备来存储Acceptor需要记住的信息来防止出错。Acceptor在真正送出响应之前,会将它记录在可靠性存储设备中。

剩下的就是需要描述保证提案编号唯一性的机制了。不同的Proposers会从不相交的编号集合中选择自己的编号,这样任何两个Proposers就不会有相同编号的提案了。每个Proposer需要将它目前生成的最大编号的提案记录在可靠性存储设备中,然后用一个比已经使用的所有编号都大的提案开始Phase1。

7 实现状态机模型

参考文献

1.http://lamport.azurewebsites.net/pubs/paxos-simple.pdf
2.https://www.iteblog.com/archives/2337.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值