Bootstrap

经典的Paxos算法

1.1 问题与需求:


我们讨论的对象是分布式系统中的一个变量。一致性问题是:要求分布在不同机器上的多个进程,对于这个变量,无论有多少提案,最多只能从众多提案中选出一个值。也就是说,无论有多少故障,例如宕机、消息延迟、乱序、重复甚至丢失(但消息不能被篡改),绝对不能有两个或者两个以上的值被选择;比如在ceph中,对于同一个epoch,绝对不能出现两个不同的osdmap,否则monitor.a认为这部分OSD挂了,而monitor.b认为那部分OSD挂了,不同客户端可能拿到不同的osdmap。另外,还要求只要有足够多的进程无故障,最终总会有一个值被选中。换言之,算法不能无限运行下去而选不出一个值。


总结来说,分布式一致性问题有两个需求:
a: 安全需求(safety requirement),它又包括两个方面:
  • 非平凡性(Nontriviality):只有提案的值才能被学习;
  • 一致性(Consistency):   最多只有一个值能被学习;学习的意思相当于:学习者(learner)"接受"被选中的值,被学习相当于这个被选中的值在系统中生效。后文会介绍学习者(leaner)角色。

b: 前进需求(progress requirement):只要有足够多的提案者(acceptor),一个提案者(proposer)和一个学习者(leaner)无故障,最终总能选出一个值,不会无限运行而选不出值。后文会介绍决策者(acceptor)、提案者(proposer)角色;


1.2 角色:

  • 提案者(proposer): 发出提案。我们讨论的对象是一个变量,提案者提议对这个变量设置某个它认为合理的值。
  • 协调者(coordinator): 是提案者的代理;在下文中,很少有提案者的事,因为提案者被协调者代理。协调者和决策者直接交互,比如发出参与请求、提案等。
  • 决策者(acceptor):负责选出一个值。决策者需要决定参不参与某一轮提案,若参与了接不接受这一轮提案。后文会详细描述它是如何工作的。
  • 学习者(leaner): 若决策者决定接受一个值,会向学习者发送这个决定;在给定的一轮中,若学习者接受到了来自"大多数决策者"的接受决定,那么这个值就被学习了(当然也被选择了)。

         需要注意的是,一个agent可以承担多个角色。论文不介绍如何实现,不过典型的场景是,一个angent充当所有角色,而只有一个活动的coordinator。


1.3 前提条件:非拜占庭模型(non-Byzantine model)
  • agent以任意速度运行,可以故障、停止、重启;但是不可以做不正确的动作;
  • agent之间的消息可以任意慢、可以重复、乱序、丢失;但不可以被破坏;

1.4 保证safty requirement --> 基本的Paxos算法



基本的Poxos算法中,只保证safty requirment,1.5中会扩展它来进一步保证progress requirment。另外,基本的Paxos分多轮进行,暂时不考虑它的终结,而假定它可以一直运行下去,但是无论运行多少轮,最终还是最多一个值被选择学习。每轮有一个唯一的编号,多轮不必按顺序进行;某一轮可以中断或者跳过;多轮可以并发。

下面直接先看算法,然后解释(证明)它是如何满足safty requirement的。

1.4.1 算法描述:

acceptor a存储的持久信息:
  • rnd[a]: acceptor a 参与的最大的round编号;0表示还未参与任何一轮。
  • vrnd[a]: acceptor a 接受的最大的提案round编号;0表示还未接受过任何提案。
  • vval[a]: acceptor a 接受的最大的提案值;若还未接受过任何提案,则无意义;
接受round r中的提案,一定参与了round r,所以 vrnd[a] <= rnd[a];反过来参与了round r,不一定接受了round r中的提案。也就是说,若参与了round r并接受了其提案,vrnd[a]=rnd[a];若参与了rund r但还未接受其提案,vrnd[a] < rnd[a];

coordinator c存储的持久信息:
  • crnd[c]: coordinator c 参与的最大的round编号;
  • cval[c]: 在round crnd[c]中,coordinator c预选的值;这个值将会发给acceptor,请求acceptor接受。若还没有预选值,则为none。

记号:
  • {i,x}:表示一个提案,round编号为i,值为x;
  • 1a消息:在步骤1a中,coordinator向acceptor发的参与请求消息;
  • 1b消息:在步骤1b中,acceptor向acceptor发的回复消息;
  • 2a消息:在步骤2a中,coordinator向acceptor发的参与请求消息;
  • 大多数集:任意一个acceptor集合,只要满足集合size大于acceptor总数的一半;

Round i 开始:proposer向coordinator c发出一个提案,{i,x};

1a: [coordinator c接收到proposer发来的提案]
    if crnd[c] < i then
        crnd[c] = i;              //更新自己参与的最大的round编号;

        cval[c] = none;           //coordinator c在round i中还没有预选值。注意,不是把x设为预选值;         

        向所有acceptor发送参与请求ParticipateRequest(内容是:i),请求他们参与;

    else
        丢弃提案{i,x};


1b: [acceptor a接受到c发出的1a消息(内容是:i)]

    if rnd[a] < i then

        rnd[a] = i;                                       //更新自己参与的最大的round编号;所以,以后不再参与

                                                          //编号小于等于i的提案;

        向coordinator c回复消息(内容是:i, vrnd[a], vval[a]) //告诉c:我保证不再参与编号小于等于i的提案;我曾经接

                                                          //受的编号最大的提案为{vrnd[a], vval[a]};

    else
        丢弃;                                             //不参与编号小于等于rnd[a]的提案;


2a: [coordinator c接收到1b消息(内容是: i, vrnd[a], vval[a])]

    if crnd[c]==i   AND                                    //此消息是对c发出的1a消息的回复,而c还没有开始更高轮

                                                           //的提案(也就是说,c正在等1a消息的回复)

       cval[c]==none AND                                   //c在round i还没有预选值(就是说,这是本轮第一次经历2a)
       c 已经接受到大多数acceptor发来的1b消息                  //可以预选值了
    then
        预选一个值v        //如何选?见下文
        cval[c] = v;      //设置预选值。
        向这些acceptor发送接受请求AcceptRequest(内容是:i,v),请求他们在round i接受预选值v;


2b: [acceptor a接受到2a消息(内容是:i,v)]
    if i >= rnd[a] AND  //没有开始更高轮的提案
       vrnd[a] != i     //在round i,还没有接受一个值(换言之,还没有为round i投票)     
    then                     
        vrnd[a] = i;    //接受提案{i,v}
        vval[a] = v;
        向所有learner发送消息,宣布它在round i中决定接受v; 若leaner接受到大多数acceptor发来的接受决定,他就学习到了v。
    else                //i<rnd[a] OR vrnd[a]==i, 已经开始了更高的round OR 在round i已经接受了一个值
        丢弃请求


为了加深理解,我们看看那些能满足这个2b中的if条件的情况:
  • i>rnd[a]>vrnd[a]:在rnd[a]没有接受值(因为rnd[a]!=vrnd[a]),i是开始的更高轮。i!=rnd[a]说明没有接收到1a消息而直接接收到了2a消息。这种情况不可能发生,因为在步骤2a中,c只向回复了1a消息的acceptor发送2a消息;
  • i>rnd[a]=vrnd[a]:在rnd[a]接受了一个值(因为rnd[a]==vrnd[a]),i是开始的更高轮。由于i!=rnd[a],此情况也不可能,理由同上。
  • i=rnd[a]>vrnd[a]:在rnd[a]没有接受值(因为rnd[a]!=vrnd[a]),i是正在进行的一轮。也就是说,已经接收到过1a消息,并向c发出了回复,这是c发出的2a消息。

再看看被选择和被学习的关系:
  1. 当大多数acceptor接受了v时,v在round i就被选择了。它们发给learner的消息可以丢失,但这只影响学习过程,而不影响v在round i被选择这一事实,因为vrnd[a]=i, vval[a]=v被持久化了;
  2. 只有被选择(大多数acceptor接受v并向learner发送接受决定),才能被学习(learner接受到大多数acceptor发的接受决定);

1.4.2 如何保证safty requirement?

  • 定义:一个值被选择,当且仅当它在某一轮中被选择;
  • 定义:一个值在某一轮中被选择,当且仅当大多数acceptor接受(accept)了这个值;

回顾一下safty requirment,它包括两个方面
  • 非平凡性(Nontriviality):只有提案的值才能被学习;
  • 一致性(Consistency):最多只有一个值能被学习;
上面的算法中,只有提案的值才可能被接受(见2b),只有接受的值才可能被选择(见2b),只有被选择的值才能被学习(见2b),所以非平凡性已经被保证;
由于只有被选择的值才能被学习,所以一致性的核心是:只有一个值被选择,即"大多数决策者"决定接受这个值。

下面分两步,先证明在同一round只可能有一个值被选择(1.4.3);然后给出一种方法,步骤2a中如何选择v(1.4.4),并证明这个方法能够保证在多个round之间只可能有一个值被选择(1.4.5)。


1.4.3 证明同一round中最多只可能一个值被选择

  • 在给定的一个round中(例如i),只有一个coordinator能够成功接到大多数acceptor的参与回复。因为对于给定的i,一个acceptor在1b中只能发出一个参与回复(后续的请求会被丢弃,因为rnd[a]=i,不满足rnd[a]<i)。
  • 一个coordinator在得到大多数acceptor的参与回复后,只能预选一个值发送给acceptor;
  • acceptor只能接受coordinator发来的值;
  • 所以,最多只有一个值被接受;
  • 只有被大多数acceptor接受的才被选择,所以最多只有一个值被选择。

1.4.4 如何保证多轮中也最多只有一个值被选择?

在算法的2a步骤中,coordinator c需要选择一个值,而它选择的这个值,应该能够保证:在多个round之间,只可能有一个值被选择。
我们先提出一个假设需求
CP:对于任意round i和round j (j<i),如果一个值v在round j中被选择或者可能被选择,那么acceptor在round i中只可能接受v。
由于一个值只有被接受才能被选择,所以CP蕴含:
 对于任意round i和round j (j<i),如果一个值v在round j中被选择或者可能被选择,那么在round i中,只可能选择v。
也就是说, CP可以保证在多个round之间,只可能有一个值被选择。已知同一round只能有一个值被选择。所以,只要我们能够满足CP,就能够满足一致性(Consistency)了

CP的等价命题:
假如有acceptor在round i中接受了值v,那么在任意round j (j<i),被选择或可能被选择的只能是v。

只有2a中选出的值才能被acceptor接受,所以,我们只需一个方法,满足:
CP(i,v): 若这个方法在2a中选出了值v,那么在任意round j(j<i),被选择或可能被选择的只能是v。(注意,在round j没有选择值也满足条件)

总之: 只要我们的方法能够保证CP(i,v),就能保证CP的等价命题,就能保证CP,进而保证一致性。

在给出这个方法,并证明它能够满足CP(i,v)之前,我们通过观察得出几个结论:

结论1:假如在round j中,值v被选择或者可能被选择,那么一定存在一个大多数集Q,满足:Q里的任意acceptor a,rnd[a]<=j,或者在round j中,a已经接受了v。
有三种情况:
  • rnd[a]<j: a还没参与round j;
  • rnd[a]=j但在round j还没有接受任何值: 已经参与了round j,但在round j还没接受任何值。在j之前的round中,可能接受了值v,也可能接受了v以外的值;
  • 在round j中已经接受了v
注意,后面只是一个必要条件,而不是充分条件。换言之,在round j中v被选择可以推出以上三种情况,但以上三种情况不能推出在round j中v被选择。它的逆否命题更容易理解:
逆否命题:假如存在一个大多数集Q,满足:Q里的任意acceptor a, rnd[a]>j并且在round j中没有接受v(可能接受了v以外的值,也可能没有接受任何值), 那么v在round j中不可能被选择。
证明:若a还没有接受任何值,它也不可能接受任何值了(因为rnd[a]>j,见算法步骤2b)。所以,对于Q里的任意acceptor a,要么a接受了v以外的值,要么不接受任何值。除Q外,即使所有acceptor在round j都接受了v,也不可能构成大多数。所以v在round j不可能被选择。

结论2(和结论1的逆否命题类似):假如存在一个大多数集Q,满足:Q里的任意acceptor a,rnd[a]>j,并且在round j中没有接受任何值,那么没有一个值可能在round j中被选择。
证明:除Q之外,即使所有acceptor都接受了某个值w,也构不成大多数,故w不可能被选择;

结论3:假如存在一个大多数集Q,满足:Q里的任意acceptor a,rnd[a]>j,并且,a在round j接受了v或者没有接受任何值,在round j被选择或可能被选择的只能是v。
证明:除Q之外,即使所有的acceptor都接受了v以外的某值w,也构不成大多数,故w不可能被选择;

现在直接给出这个方法,后面再证明这个方法能够满足CP(i,v):
见算法步骤2a,这时coordinator c已经接收到来大多数的acceptor的1b消息(假设这个大多数集为Q,size为N),这些消息是:
i, vrnd[a1], vval[a1]
i, vrnd[a2], vval[a2]
...
i, vrnd[aN], vval[aN]
 
假设vrnd[aX]是vrnd[a1],vrnd[a2],...,vrnd[aN]中最大的,对应的值为vval[aX](若有多个最大的,他们对应的值也是同一个,因为在同一round,只有一个coordinator能够得到大多数1b消息,而它又只请求接受一个值)。设k=vrnd[aX]。那么有两种情况:

  • k=0: 选择proposer提案的x;
  • k>0: 选择vval[aX];

1.4.5 证明这个选择方法能够满足CP(i,v).

k=0时:对于Q里的任意acceptor a,a在round i之前没有接受过任何值。形式化的说,对于任意round j (j<j),rnd[a]>j(因为rnd[a]>=i),并且在round j没有接受任何值。根据结论2,在任意round j(j<i),没有值在round j被选择。这时,coordinator c可以任意选择一个值v,都满足CP(i,v)。所以,c选择proposer提案的值x。


k>0时:因为acceptor在1b中,只有i>rnd[a]时才回复,并且rnd[a]>=vrnd[a]。所以对于任意1b消息(i, vrnd[a], vval[a]),都有i>vrnd[a]。所以k<i。我们要证明CP(i,v): 既然2a中选出了值vval[aX](记为v),那么在任意round j(j<i),被选择或可能被选择的只能是v=vval[aX]。对j,有三种情况:

  • k<j<i: 对于Q里的任意acceptor a,a在回复1b消息时,肯定还没有为round j接受一个值(因为若接受了,vrnd[a]=j,在coordinator c接收到的那些1b消息中,至少有vrnd[aR]=j,这和k是最大值矛盾)。而这时rnd[a]>j(因为a在步骤1a中已经回复了1b消息,故rnd[a]>=i,故rnd[a]>j)。根据结论2,在round j中没有选择任何值。所以满足C(i,v)。说白了,round j到现在还没有选择一个值,而大多数rnd[a]已经大于j,不可能再为round j接受一个值,所以,round j不可能选择一个值了。
  • j=k: 对于Q里的任意acceptor a,rnd[a]>j(因为a在步骤1a中已经回复了1b消息,故rnd[a]>=i,i>k=j,故rnd[a]>j),并且,a在round j中要么接受了v=vval[aX](若vrnd[a]=k),要么没有接受任何值(若rnd[a]<j),根据结论3,在round j被选择或可能被选择的只能是v=vval[aX]。
  • j<k: 如果w(w!=v)在round j被选择:那么在round k中,步骤2a中一定会预选w,所以round k不可能选择v,矛盾;也可以通过归纳法证明:因为在round k中接受了值v,那么在round j (j<j)中,被选择或可能被选择的只能是v。 也就是说,在众多小于k的1b消息中,有可能有多个值,但是没有一个被选择或可能被选择。 


至此,我们已经完成了paxos算法safty requirement的证明,后续的文章中会介绍如何满足progress requirement。

;