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)角色;
- 提案者(proposer): 发出提案。我们讨论的对象是一个变量,提案者提议对这个变量设置某个它认为合理的值。
- 协调者(coordinator): 是提案者的代理;在下文中,很少有提案者的事,因为提案者被协调者代理。协调者和决策者直接交互,比如发出参与请求、提案等。
- 决策者(acceptor):负责选出一个值。决策者需要决定参不参与某一轮提案,若参与了接不接受这一轮提案。后文会详细描述它是如何工作的。
- 学习者(leaner): 若决策者决定接受一个值,会向学习者发送这个决定;在给定的一轮中,若学习者接受到了来自"大多数决策者"的接受决定,那么这个值就被学习了(当然也被选择了)。
- 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 接受的最大的提案值;若还未接受过任何提案,则无意义;
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消息。
再看看被选择和被学习的关系:
- 当大多数acceptor接受了v时,v在round i就被选择了。它们发给learner的消息可以丢失,但这只影响学习过程,而不影响v在round i被选择这一事实,因为vrnd[a]=i, vval[a]=v被持久化了;
- 只有被选择(大多数acceptor接受v并向learner发送接受决定),才能被学习(learner接受到大多数acceptor发的接受决定);
1.4.2 如何保证safty requirement?
- 定义:一个值被选择,当且仅当它在某一轮中被选择;
- 定义:一个值在某一轮中被选择,当且仅当大多数acceptor接受(accept)了这个值;
回顾一下safty requirment,它包括两个方面
- 非平凡性(Nontriviality):只有提案的值才能被学习;
- 一致性(Consistency):最多只有一个值能被学习;
由于只有被选择的值才能被学习,所以一致性的核心是:只有一个值被选择,即"大多数决策者"决定接受这个值。
下面分两步,先证明在同一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
逆否命题:假如存在一个大多数集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。