数据库_paxos算法

Paxos算法是莱斯利·兰伯特于1990年提出的一种基于消息传递且具有高度容错特性的共识(consensus)算法。

需要注意的是,Paxos常被误称为“一致性算法”。但是“一致性(consistency)”和“共识(consensus)”并不是同一个概念。Paxos是一个共识(consensus)算法。

首先:忽略消息会被篡改或者是错误消息问题。

Paxos解决的问题背景

  在分布式系统中可能会出现进程可能会慢、被杀死或者重启,消息可能会延迟、丢失、重复等问题。Paxos 算法解决的问题是在一个可能发生上述异常的分布式系统中如何就某个值达成一致,保证不论发生以上任何异常,都不会破坏决议的共识。

Paxos中的角色

  Proposer(提案者):提案发出者。

      选主过程就是各个实体作为Proposer发挥此角色作用的过程。

  Acceptors(接受者):接受提案者。

  Learners(学习者):多数派之外的少数派。

  -----------------------------------------------------------------------------------------------------------------------

  Leader(领导者):发出的提案被大多数实体接收后的实体。在一个Paxos组中,只有一个Leader。

  -----------------------------------------------------------------------------------------------------------------------

值的选定

  一个提议者将一个提议的值发送给一群接受者。一个接受者可能接受(accept)这个被提议的值。一旦一个足够大数量(大多数:N/2+1)的接受者的集合都接受了一个值,那么这个值就可以算是被选定了。因为任意两个“大多数”的集合必然拥有至少一个共同的接受者,并且假如一个接受者最多只能接受一个值,这个方法就是行得通的。 

  在不考虑(系统)故障或者消息丢失的情况下,我们期望在哪怕只有一个提议者提出值的时候也能选定一个值。这引出了条件:

P1. 接受者必须接受它收到的第一个提案。

  在P1条件下存在一个问题:在几乎同一时间多个不同提议者可能提议多个不同的值,导致每个接受者都接受了其中一个值,但是没有任何一个值被接受者中的“大多数”所接受

  结合P1和”大多数“要求,得到条件:必须允许接收者接受不止一个提案

  由此我们使用对每个提案进行(自然)编号的方式来跟踪接受者可以接受的不同的提案,这样的话,每个提案都包含了一个提案编号以及对应的值。为了防止混淆,我们要求不同的提案必须要有不同的编号。至于怎么实现不同的编号则取决于实现的方案,这里我们只要做假设就好了。当一个带有某个值的提案被大多数的接受者接受了之后,这个值就算是被选定了。在这种场景下,我们可以说这个提案(以及它的值)已经被选定了。

  引出条件:

P2. 如果一个拥有值 v 的提案被选定,则每一个(比这个提案)更高编号且被选定的提案也都拥有值 v

  为了被选定,一个提案必须被至少一个接受者所接受。引出条件:

P2a. 如果一个拥有值 v 的提案被选定,则每一个(比这个提案)更高编号且被任意一个接受者接受的提案也都拥有值 v

  因为消息通信是异步的,一个提案可能会被某个从来没有收到过任何提案的特殊接受者 c 所接受。设想一个新的提议者“醒来”并且提议了一个更高编号且值不同的提案的场景。P1要求 c 不得不接受这个提案,但这又会打破 P2a 的条件。为了同时满足 P1 和 P2a,需要对 P2a 做进一步加强:

P2b. 如果一个拥有值 v 的提案被选定,则每一个(比这个提案)更高编号且被任意一个提议者提议的提案也都拥有值 v

  为了发现如何满足 P2b,我们考虑如何证明它成立。我们先假设某个编号为m,且值为 v 的提案已经被选定,然后证明任何编号为 n (n > m)的提案也都拥有值 v。我们可以通过对 n 采用数学归纳法以使证明过程更轻松,于是在以下额外的假设下可证明编号为 n 的提案拥有值 v

归纳假设:每一个编号在 m..(n-1) 之间的提案都拥有值 v,这里的 i..j 的记法代表从 i 到 j 的一组编号。

由于编号为 m 的提案已经被选定,那就必然存在一个由“大多数”接受者组成的集合 C,且集合里的每一个接受者都接受了这个提案。结合这个(推理)以及前面的归纳假设,m 被选定的假设则意味着:

集合 C 里的每个接受者都接受了编号在 m..(n-1) 之间的一个提案,并且被任何一个接受者所接受的每一个编号在 m..(n-1) 之间的提案都拥有值 v。

由于任意一个由大多数接受者组成的集合 S 都必然包含集合 C 的至少一个元素,我们可以通过维护以下不变性以保证编号为 n 的提案拥有值 v

P2c. 对于任意的 v 和 n,如果一个编号为 n 且拥有值 v 的提案被提议,则存在一个由大多数接受者组成的集合 S 满足这里其中一个条件:
  (a)集合 S 里没有接受者接受了任何一个编号小于 n 的提案;
  或者是:
  (b)v 是集合 S 中的接受者已经接受过的所有编号小于 n 的提案中编号最高的提案的值。

  为了维护 P2c 的不变性,想要提议编号为 n 的提案的提议者必须获知编号小于 n 的最大编号的提案,如果存在这样的提案的话,那它肯定是已经或者即将被大多数接受者所接受的提案。获知已经被接受的提案是足够简单的,但是预测未来哪些(提案会被)接受则是困难的。与其尝试去预测未来,不如让提议者通过获取一个“将不会存在任何一个这样的接受”的承诺来控制这个过程。换句话说,提议者请求接受者们不再接受任何编号比 n 小的提案。这就引出了以下用于提议过程的算法:

  1. 一个提议者选择一个新的提案编号 n,然后给由某些接受者组成的集合中的每一个成员发送一个请求,要求它响应以下信息:
    a. 一个承诺:不再接受任何一个编号比 小的提案,并且
    b. 如果它已经有接受过提案的话,则还要返回它已经接受过的编号比 n 小的最大编号的提案
    我把这样一个请求称之为对编号 n 的 prepare 请求。
  2. 如果提议者从大多数的接受者成功收到期待的响应,则它可以接着提议一个编号为 n 且值为 v 的提案,这里 v 就是它从1b 中收到的响应里最大编号的提案的值,如果所有响应都表明没有接受过任何提案,则提议者可以自由选择一个值。
    提议者通过向一组接受者发送一个请求来提议提案。(这里的这组接受者并不需要和响应 request 请求的接受者一致)。让我们把这个请求称之为 accept 请求。

  对于接受者,它可以接收来自提议者的两种请求:prepare 请求和 accept 请求。接受者可以忽略任何请求而不影响安全性。所以,我们需要讨论只在哪些情况下它可以响应请求。它总会响应 prepare 请求;它也可以响应 accept 请求,接受提案,只要它(事先)没有承诺不这样做。换句话说:

P1a. 接受者可以接受编号为 n 的提案,只要它没有响应过编号大于 n 的 prepare 请求。

  当一个接受者已经响应过一个编号为n的prepare请求,则它不会接受比n小的提案请求。所以我们让接受者直接忽略这样一个 prepare 请求。我们也让接受者直接忽略它已经接受的提案的 prepare 请求。

  加上这个优化,接受者只需要记录它已经接受过的最高编号的提案以及它已经响应过的最高编号的 prepare 请求的编号即可。因为无论失败与否,P2c 都必须保持不变,所以接受者必须能够记录这些信息,哪怕它可能崩溃,以及重启。注意提议者总是可以放弃某个提案并且装作什么都没有发生过——只要提议者不会尝试用相同的编号提议另一个提案。

  将提议者和接受者的行为都放在一起,我们可以看到这个算法的操作可以分为以下两个阶段:

阶段 1:
(a)提议者选择一个提案编号 n,向“大多数”接受者发送一个带有编号 n 的 prepare 请求;
(b)如果接受者收到一个编号为 n 的 prepare 请求,且 n 比它已经响应过的任何一个 prepare 请求的编号都大,则它会向这个请求回复响应,内容包括:一个不再接受任何编号小于 n 的提案的承诺,以及它已经接受过的最大编号的提案(假如有的话)。

阶段 2:
(a)如果提议者从“大多数”接受者收到了对它前面发出的 prepare 请求的响应,它就会接着给那每一个接受者发送一个针对编号为 n 且值为 v 的提案的 accept 请求,而 v 就是它所收到的响应中最大编号的提案的值,或者是它在所有响应都表明没有接受过任何提案的前提下自由选择的值 v;
(b)如果接受者收到了一个针对编号为 n 的提案的 accept 请求,它就会接受这个请求,除非它之前已经响应过编号大于 n 的 request 请求。

  如果接受者由于它自身已经收到了更高编号的 prepare 请求而选择忽略(当前的)prepare 或者 accept 请求,那它应该通知提议者,提议者应该在收到通知后放弃提案。

获知选定的值(Learner)

  为了获知值已被选定,学习者必须找出某个已经被大多数接受者接受的提案。最显而易见的算法就是让每一个接受者一旦接受了提案,就响应给所有学习者,并给它们发送接受了的提案信息。这种方法允许学习者们尽可能快地找出被选定的值,但这种方法也要求每个接受者要响应每个学习者——响应的数量等于接受者数量和学习者数量的乘积。

  没有拜占庭式错误的这样一个假设使得一个学习者可以很容易地通过其他的学习者来获知某个值已被接受的事实。我们可以让接受者将它们的接受事件响应给某个特定的学习者,这个特定的学习者要负责在每次一个值被选定之后通知其他的多个学习者。这种方法要求所有的学习者花费额外一轮的时间用于获知被选定的值,也降低了可靠性,因为那个特定的学习者可能会故障。但是这个方法要求的响应数量只等于接受者的数量和学习者的数量之和。

  更一般的,接受者可以将它们的接受事件响应给由多个特定的学习者组成的某个集合,集合中的每个学习者都会在每次一个值被选定之后通知所有的学习者。使用这样一个较大的特定的学习者组成的集合可以在更大的通信复杂度上提供更大的可靠性。

  由于消息丢失,值可能在学习者无法发现的情况下被选定。学习者可以询问接受者:现在已经接受了什么提案?但是接受者的失效可能导致不可能知道是否有一个大多数的(接受者)已经接受了某个特定的提案。在那种场景下,学习者只能在一个新的提案被选定的情况下才能找出哪个值被选定了。如果学习者需要知道一个值是否已经被选定,它可以让提议者使用上面描述的算法提议一个提案即可。

可行性

  构建这样一个场景是容易的:两个提议者相继提议一系列递增编号的提案,但是没有哪一个提案能被选定。提议者 p 完成了编号 n1 的提案的阶段1,接着另一个提议者 q 也完成了编号 n2n2 > n1)的提案的阶段1.由于接受者已经承诺不会再接受任何编号小于 n2 的新提案,所以提议者 p 在阶段2为提案 n1 发送的 accept 请求会被忽略。所以,提议者 p 又接着开始并且完成了一个新的提案 n3n3 > n2)的阶段1,导致提案 q 的阶段2的 accept 请求也被忽略了,以此类推……

  为了保证(过程)可进行,一个特定的提议者必须被选为唯一一个提议提案的。如果这个特定的提议者可以成功地和大多数接受者通信,并且它使用了编号比任何已经使用的编号大的提案,那么它将会成功完成提议,也就是说,提案会被接受。通过在发现某个请求已经使用了更高的提案编号的情况下主动放弃提案然后重试(阶段1),这个特定的提议者终将能够选到一个足够高的提案编号。

  如果系统有足够多的组件(提议者、接受者以及通信网络)正常工作,那么就可以通过选举一个单一的特定的提议者来实现活性。Fischer, Lynch 和 Patterson 的著名(实验)结果指出:

  选举一个提议者的可靠算法必须使用随机性或者实时性——举例来说,使用超时机制。无论如何,不管选举成功或者失败,安全性都是可以保证的

实现

  Basic Paxos的工作流程:

Client   Proposer      Acceptor     Learner
   |         |          |  |  |       |  | --- First Request ---
   X-------->|          |  |  |       |  |  Request
   |         X--------->|->|->|       |  |  Prepare(N)
   |         |<---------X--X--X       |  |  Promise(N,I,{Va,Vb,Vc})
   |         X--------->|->|->|       |  |  Accept!(N,I,V)
   |         |<---------X--X--X------>|->|  Accepted(N,I,V)
   |<---------------------------------X--X  Response
   |         |          |  |  |       |  |

  跳过阶段1时的Multi-Paxos

Client   Proposer      Acceptor     Learner
   |         |          |  |  |       |  | --- First Request ---
   X-------->|          |  |  |       |  |  Request
   |         X--------->|->|->|       |  |  Prepare(N)
   |         |<---------X--X--X       |  |  Promise(N,I,{Va,Vb,Vc})
   |         X--------->|->|->|       |  |  Accept!(N,I,V)
   |         |<---------X--X--X------>|->|  Accepted(N,I,V)
   |<---------------------------------X--X  Response
   |         |          |  |  |       |  |  ----一轮流程

 

  

 

参考资料

  1 https://segmentfault.com/a/1190000037628341

  2 https://ws.wiki.fallingwaterdesignbuild.com/baike-Paxos%E7%AE%97%E6%B3%95?wprov=srpw1_0

  3 《分布式数据库原理、架构与实践》

  

 

 
posted @ 2022-01-04 23:01  学不动了orz  阅读(260)  评论(0)    收藏  举报