大纲
1.分布式系统特点
2.分布式系统的理论
3.两阶段提交Two-Phase Commit(2PC)
4.三阶段提交Three-Phase Commit(3PC)
5.Paxos岛的故事来对应ZooKeeper
6.Paxos算法推导过程
7.Paxos协议的核心思想
8.ZAB算法简述
6.Paxos算法推导过程
(1)Paxos的概念
(2)问题描述
(3)推导过程
(4)Proposer生成提案
(5)Acceptor批准提案
(6)Paxos算法描述
(7)Learner学习被选定的value
(8)如何保证Paxos算法的活性
(1)Paxos的概念
一.Paxos的角色
二.Paxos的提案
三.Paxos角色对数据达成一致的情况
一.Paxos的角色
在Paxos算法中,有三种角色:Proposer、Acceptor、Learner。在具体的实现中,一个进程可能同时充当多种角色。比如一个进程可能既是Proposer又是Acceptor又是Learner。
还有一个重要概念叫提案(Proposal),最终要达成一致的value就在提案里。
假如只有一个角色,那么其实就是分布式节点自己。各自认为自己的表达是正确的,这时候是无法达成一致的。所以需要引入多一个角色来处理各个节点的表达,最后还要引入一个角色将达成一致的结果同步给各分布式节点。
二.Paxos的提案
暂且认为提案(Proposal)只包含value,但在接下来的推导过程中会发现如果提案(Proposal)只包含value会有问题。
暂且认为Proposer可以直接提出提案,在接下来的推导过程中会发现:如果Proposer直接提出提案也会有问题,需要增加一个学习提案的过程。
Proposer可以提出(propose)提案,Acceptor可以批准(accept)提案。如果某个提案被选定(chosen),那么该提案里的value就被选定了。
注意:批准和选定是不一样的,批准未必就代表选定。根据后面所述,多数Acceptor批准了某提案,才能认为该提案被选定了。
三.Paxos角色对数据达成一致的情况
对某个数据的值达成一致,指的是:Proposer、Acceptor、Learner都认为同一个value被选定(chosen)。
Proposer、Acceptor、Learner分别在什么情况才认为某个value被选定:
情况一:Proposer
只要Proposer发出的提案被Acceptor批准,那么Proposer就认为该提案里的value被选定了。
情况二:Acceptor
只要Acceptor批准了某个提案,那么Acceptor就认为该提案里的value被选定了。
情况三:Learner
Acceptor告诉Learner哪个value被选定,Learner就认为那个value被选定。
上面只有一个节点时是没问题的,但多个节点时批准就不能等于选定了。
(2)问题描述
一.该一致性算法的基本实现目标
二.该一致性算法满足的安全性
三.推导该一致性算法的默认条件
四.提案被选定的规定
一.该一致性算法的基本实现目标
假设有一组可以提出(propose)value的进程集合(value在提案Proposal里),那么一个一致性算法需要做出如下保证。
保证一:在提出的这么多value中,只有一个value会被选定(chosen)
保证二:如果没有value被提出,那么就不应该有value被选定
保证三:如果一个value被选定,那么所有进程都应该能学习(learn)或者获取到这个被选定的value
二.该一致性算法满足的安全性
一个分布式算法有两个最重要的属性:安全性(Safety)和活性(Liveness)。安全性是指那些需要保证永远都不会发生的事情,活性是指那些最终一定会发生的事情。
对于一致性算法,安全性要求如下:
要求一:只有被提出的value才能被选定
要求二:只有一个value能被选定
要求三:如果某进程认为某value被选定,则该value必须是真的被选定
在对Paxos算法的介绍中,我们不去精确定义其活性需求,只需要确保:Paxos算法的目标是保证最终有一个提出的value被选定,当一个value被选定后,进程最终也能学习或者获取到这个value。
三.推导该一致性算法的默认条件
假设三个角色间可通过发送消息来进行通信,那么默认以下两个情况:
情况一:每个角色以任意的速度执行,可能因出错而停止,也可能会重启。同时即使一个value被选定后,所有的角色也可能失败然后重启。除非失败后重启的角色能记录某些信息,否则重启后无法确定被选定的值。
情况二:消息在传递过程中可能任意延迟、可能会重复、也可能丢失。但是消息不会被损坏,即消息内容不会被篡改,也就是无拜占庭将军问题。即各将军管理的军队被地理分割开,只能依靠通讯员传递信息,而通信员可能存在叛徒篡改信息欺骗将军。
四.提案被选定的规定
下面规定在存在多个Acceptor的情况下,如何判断选定一个提案。
规定:Proposer会向一个Acceptor集合发送提案。同样,集合中的每个Acceptor都可能会批准(Accept)该提案。当有足够多的Acceptor批准这个提案时,我们就认为该提案被选定了。
所以,批准和选定是不一样的,批准未必就代表选定。多数Acceptor批准了某提案,才能认为该提案被选定了。
足够多指的是:我们假定足够多的Acceptor其实是整个Acceptor集合的一个子集,并且让这个集合大得可以包含Acceptor集合中的大多数成员,因为任意两个包含大多数Acceptor的子集至少有一个公共成员。
(3)推导过程
一.要选定一个唯一提案的最简单方案——只允许一个Acceptor存在
二.多个Acceptor和多个Proposer——如何使得只有一个value被选定
三.提案变成了一个由编号和value组成的组合体:[编号, value]
四.如何证明P2b
五.如何根据P2c去证明P2b
约束P1:一个Acceptor必须批准它收到的第一个提案。
规定R1:一个Proposer的提案能够发送给多个Acceptor(Acceptor集合)。
规定R2:一个提案(value)被选定需要被半数以上的Acceptor批准。
规定R3:每一个Acceptor必须能够批准不止一个提案(value)。约束P2:如果提案[M0, V0]被选定了:
那么所有比编号M0更高的且被选定的提案,其value值必须也是V0。约束P2a:如果提案[M0, V0]被选定了:
那么所有比编号M0更高的且被Acceptor批准的提案,其value值必须也是V0。约束P2b:如果提案[M0, V0]被选定了:
那么之后任何Proposer产生的编号更高的提案,其value值必须也是V0。约束P2c:对于任意的Mn和Vn,如果提案[Mn, Vn]被提出:
那么肯定存在一个半数以上的Acceptor组成的集合S,满足以下条件中的任意一个:条件一:S中每个Acceptor都没有批准过编号小于Mn的提案。
条件二:S中Acceptor批准过的编号小于Mn的且编号最大的提案的value为Vn。
一.要选定一个唯一提案的最简单方案——只允许一个Acceptor存在
要使得只有一个value被选定,最简单的方式莫过于只有一个Acceptor,当然可以有多个Proposer,这样Proposer只能发送提案给该Acceptor。
此时,Acceptor就可以选择它收到的第一个提案作为被选定的提案,这样就能够保证只有一个value会被选定。
但是,如果这个唯一的Acceptor宕机了,那么整个系统就无法工作。因此,必须要有多个Acceptor来避免Acceptor的单点问题。
二.多个Acceptor和多个Proposer——如何使得只有一个value被选定
如果希望即使只有一个Proposer提出一个value,该value也能被选定。那么,就得到下面的约束:
约束P1:一个Acceptor必须要批准它收到的第一个提案。
但是,这又会引出另一个问题:如果每个Proposer分别提出不同的value,发给不同的Acceptor。根据约束P1,每个Acceptor分别批准自己收到的第一个value,这就会导致不同的value被选定,出现value不一致。于是满足不了只有一个value会被选定的要求,如下图示:
上面是由于:"一个提案只要被一个Acceptor批准,则该提案的value就被选定了",以及"Acceptor和Proposer存在多个",才导致value不一致问题。因此问题转化为:在存在多个Acceptor和多个Proposer情况下,如何进行提案的选定?
最简单的情况:如果一个Proposer的提案只发送给一个Acceptor,由上图可知必然会导致value不一致问题。因此,可以有以下规定:
规定R1:一个Proposer的提案能够发送给多个Acceptor(Acceptor集合)。
既然一个Proposer的提案能够发送给多个Acceptor,当Proposer可以向Acceptor集合发送提案时,集合中的每个Acceptor都可能会批准该提案。当有足够多的Acceptor批准这个提案时,我们才可认为该提案被选定了。那么什么才是足够多呢?
我们假定足够多的Acceptor是整个Acceptor集合的一个子集,并且让该子集大得可以包含Acceptor集合中的大多数成员,因为任意两个包含大多数Acceptor的子集至少有一个公共成员。因此,有了如下规定:
规定R2:一个提案(value)被选定需要被半数以上的Acceptor批准。
在约束P1(一个Acceptor必须要批准它收到的第一个提案)的基础上,再加上规定R2(一个提案(value)被选定需要被半数以上的Acceptor批准),假如每个Acceptor最多只能批准一个提案,那么又回到了下图的问题:
因此,有了如下规定:
规定R3:每一个Acceptor必须能够批准不止一个提案(value)。
既然一个Proposer的提案能够发送给多个Acceptor,那么一个Acceptor就会收到多个提案。
考虑情形1:当多个Proposer将其提案发送给多个Acceptor后,突然大部分Acceptor挂了,只剩一个Acceptor存活,如何进行提案的批准。该存活的Acceptor收到多个提案,由规定R3,它可以批准多份提案,那么如何保证最后只有一个提案被选定保证value一致。
考虑情形2:有5个Acceptor,其中2个批准了提案v1,另外3个批准了提案v2。此时如果批准v2的3个Acceptor中有一个挂了,那么v1和v2的批准者都变成了2个,此时就没法选定最终的提案。
因此,可以引入全局唯一编号来唯一标识每一个被Acceptor批准的提案:当一个具有某value值的提案被半数以上的Acceptor批准后,我们就认为该value被选定了,也就是该提案被选定了。唯一编号的作用其实就是用来辅助:当出现多个value都被同一半数批准时,可以选定唯一的value,比如选择唯一编号最大的value。
三.提案变成了一个由编号和value组成的组合体:[编号, value]
由上可知,选定其实认的只是value值。当提案[value]变成[编号, value]后,是允许多个提案[编号, value]被选定的。根据规定R2(一个提案被选定需要半数以上的Acceptor批准),存在多个提案被半数以上的Acceptor批准,此时就有多个提案被选定。那么就必须保证所有被选定的提案[编号, value]都具有相同的value值。否则,如果被选定的多个提案[编号, value]其value不同,又会出现不一致。因此,可以得到如下约束:
约束P2:如果提案[M0, V0]被选定了,那么所有比编号M0更高的且被选定的提案,它的value值也必须是V0。
编号可理解为提案被提出的时间,比编号M0更高可理解为晚提出。所以提案[M0,V0]被选定后,所有比该提案晚提出的提案被选定时。为了value一致,那么这些晚提出的被选定的提案的value也要是V0。而所有比该提案早提出的提案,可以忽视它不进行选定即可。
一个提案[编号, value]只有被Acceptor批准才可能被选定,因此我们可以把约束P2改写成对"Acceptor批准的提案"的约束P2a:
约束P2a:如果提案[M0, V0]被选定了,那么所有比编号M0更高的且被批准的提案,它的value值也必须是V0。
所以提案[M0,V0]被选定后,所有比该提案晚提出的提案被批准时。为了value一致,那么这些晚提出的被批准的提案的value也要是V0。而所有比该提案早提出的提案,可以忽视它不进行批准即可。因此只要满足了P2a,就能满足P2。
由于通信是异步的,一个提案可能会在某个Acceptor还未收到任何提案时就被选定了。假设有5个Acceptor和2个提案,在Acceptor1没收到任何提案情况下,其他4个Acceptor已经批准了来自Proposer2的提案。而此时Proposer1产生了一个具有其他value值的且编号更高的提案,并发送给了Acceptor1。那么根据P1,Acceptor1要批准该提案,但这与约束P2a矛盾。
因此,如果要同时满足P1和P2a,需要对P2a进行强化。因为P2a只是说晚提出的提案被Acceptor批准时value才为V0,需要对P2a强化为晚提出的提案不管是否被批准其value都是V0。
约束P2b:如果提案[M0, V0]被选定了,那么之后任何Proposer产生的编号更高的提案,其value值必须也是V0。
否则一个提案得到多数批准被选定后,再提出一个值不同的新提案,这个提案会作为第一个提案发到某个Acceptor然后被批准,与P2a矛盾。
因为一个提案必须在被Proposer提出后才能被Acceptor批准,所以P2b可以推出P2a,P2a可以推出P2。
P2:提案[M,V]被选定后,晚提出的提案如果被选定,那么其value也是V;(否则会出现选定多个value了)
P2a:提案[M,V]被选定后,晚提出的提案如果被批准,那么其value也是V;(P2a可以推出P2)
P2b:提案[M,V]被选定后,晚提出的提案不管是否被批准,那么其value也是V;(否则根据P1可能会出现与P2a矛盾的情况)
而对于比提案[M,V]早提出的提案,可以采取忽略无视处理;(P2b可以推出P2a)P1:一个Acceptor必须批准它收到的第一个提案;(否则只有一个提案被提出时就无法选定一个value了)
R1:一个Proposer的提案能够发送给多个Acceptor(Acceptor集合);(否则根据P1, 就会出现选定多个value了)
R2:一个提案(value)被选定需要被半数以上的Acceptor批准;(选定提案时对足够多的规定)
R3:每一个Acceptor必须能够批准不止一个提案(value);(否则一个提案就没法做到被半数以上的Acceptor批准了)
四.如何证明P2b
即提案[M0,V0]被选定后,Proposer提出的编号更高的提案的value都为V0。如果要证明P2b成立,则具体是要证明:假设提案[M0,V0]已被选定,则编号大于M0的提案Mn,其value值都是V0。
通过对Mn使用第二数学归纳法来证明,也就是说需要证明结论:假设编号在M0到Mn-1之间的提案,其value值都是V0,那么编号为Mn的提案的value值也为V0。
第二数学归纳法:
要证明n时的value值为v,则先假设0~n-1时的value值都为v;
然后再推导出n时的value值为v;
证明:
根据第二数学归纳法,当提案[M0,V0]被选定时,要证明编号大于M0的提案Mn,其value值都是V0。也就是假设编号M0到Mn-1的提案的value值都是V0时,证明Mn的value值为V0。
因为编号为M0的提案已经被选定了,这意味着存在一个由半数以上的Acceptor组成的集合C,C中的每个Acceptor都批准了编号为M0的提案。
根据归纳假设,编号为M0的提案被选定意味着:C中的每个Acceptor都批准了一个编号在M0到Mn-1范围内的提案,并且每个编号在M0到Mn-1范围内的被Acceptor批准的提案,其value为V0。
根据归纳假设,因为编号M0到Mn-1的提案的value值都是V0,所以C中的Acceptor1可以批准M0提案,Acceptor2可以批准M0 + M1提案,Acceptor3可以批准M0 + M1 + M2 + M3提案......也就是C中每个Acceptor批准的一个M0到Mn-1范围内的提案的value都是V0。
因为任何包含半数以上Acceptor的集合S都至少包含C中的一个成员,所以S中必然存在一个Acceptor,它批准的M0到Mn-1提案的value都是V0。这是根据归纳假设得出的结论,因此可以根据此而进一步加强到P2c。
因此只要满足如下P2c,就能让编号为Mn的提案的value值也为V0,也就是只要满足P2c,就可以证明P2b。只要满足如下P2c约束 + 上述归纳假设得出的结论,就能证明Mn也为V0。
约束P2c:对于任意的Mn和Vn,如果提案[Mn, Vn]被提出,则存在一个半数以上的Acceptor组成的集合S,满足以下条件中的任一个。
条件一:S中每个Acceptor都没有批准过编号小于Mn的提案
条件二:S中Acceptor批准过的编号小于Mn且编号最大的提案的value为Vn
五.如何根据P2c去证明P2b
从P1到P2c的过程其实是对一系列条件的逐步加强。如果需要证明这些条件可以保证一致性,那么就要反向推导:P2c => P2b => P2a => P2,然后通过P1和P2来保证一致性。
实际上,P2c规定了每个Propeser应该如何产生一个提案(P2c规定的提案生成规则)。对于产生的每个提案[Mn, Vn],需要满足:存在一个由超过半数的Acceptor组成的集合S满足以条件的任意一个:
条件一:要么S中没有Acceptor批准过编号小于Mn的任何提案
条件二:要么S中所有Acceptor批准的所有编号小于Mn的提案中,编号最大的那个提案的value值为Vn
当每个Proposer都按照这个规则来产生提案时,就可以保证满足P2b了。
下面在P2c的生成规则下证明P2b:
首先假设提案[M0,V0]被选定了,设比该提案编号M0大的提案为[Mn,Vn],那么在P2c的生成提案规则前提下,证明Vn = V0;
数学归纳法第一步:验证某个初始值成立
当Mn = M0 + 1时,如果有这样一个编号为Mn的提案,根据P2c的提案生成规则可知,一定存在一个超半数Acceptor的子集S,由于提案[M0, V0]已被选定,所以S中必然有Acceptor批准过编号小于Mn的提案,也就是M0提案,即此时P2c的提案生成规则的条件一不成立,进入条件二来生成Vn。
所以,由于S中有Acceptor已经批准了编号小于Mn的提案(即M0提案)。于是,Vn只能是多数集S中编号小于Mn但为最大编号的那个提案的值。而此时因为Mn = M0 + 1,因此理论上编号小于Mn但为最大编号的那个提案肯定是[M0, V0],同时由于S和选定[M0, V0]的Acceptor集合都是多数集,故两者肯定有交集,也就是说由于两者都是多数集,所以S中必然存在一个Acceptor批准了M0。根据Mn = M0 + 1,M0其实就是编号小于Mn但是编号是最大的。这样Proposer在确定Vn取值的时候,就一定会选择V0(根据Vn只能是多数集S中编号小于Mn但为最大编号的那个提案的值)。
数学归纳法第二步:假设编号在M0 + 1到Mn - 1内成立,推导编号Mn也成立
根据假设,编号在M0 + 1到Mn - 1区间内的所有提案的value值为V0,需要证明的是编号为Mn的提案的value值也为V0。
由于编号在M0 + 1到Mn - 1区间内的所有提案都是按P2c的规则生成的,所以一定存在一个超半数Accepter的子集S,而且S中有Acceptor已经批准了编号小于Mn的提案(P2c条件一不成立)。于是,Vn只能是多数集S中编号小于Mn但为最大编号的那个提案的值。如果这个最大编号落在M0 + 1到Mn - 1区间内,那么Vn肯定是V0。如果不落在M0 + 1到Mn - 1区间内,则它的编号不可能比M0小,肯定是M0。这时因为S肯定会与批准[M0, V0]这个提案的Acceptor集合S'有交集,也就是说S中肯定存在一个Acceptor是批准了[M0, V0]的。又由于此时S中编号最大的提案其编号就是M0,根据上述,Vn只能是多数集S中编号小于Mn但为最大编号的那个提案的值。从而可知,此时Vn也是V0,因此得证。
(4)Proposer生成提案
在P2c的基础上,如何进行提案的生成?
对于一个Proposer来说,获取那些已经被通过的提案远比预测未来可能会被通过的提案简单。所以Proposer产生一个编号为M的提案时,必须要知道:当前某个已被半数以上Acceptor批准的编号小于M但为最大编号的提案,必须要求:所有的Acceptor都不要再批准任何编号小于M的提案。于是就引出了如下Proposer生成提案的算法:
步骤一:Proposer选择一个新的编号M向某Acceptor集合的成员发送请求,即编号为M的提案的Prepare请求,要求集合中的Acceptor做出两个回应。回应一是:向Proposer承诺,保证不再批准任何编号小于M的提案。回应二是:如果Acceptor已经批准过任何提案,那么就向Proposer反馈当前其已批准的、编号小于M但为最大编号的提案。
步骤二:如果Proposer收到了来自半数以上的Acceptor的响应结果,那么就可产生提案[M, V],这里V取收到响应的编号最大的提案的value值。当然还存在另外一种情况,就是半数以上的Acceptor都没有批准任何提案。也就是响应中不包含任何提案,那么此时V就可以由Proposer任意选择。Proposer确定好生成的提案[M, V]后,会将该提案再次发送给某个Acceptor集合,并期望获得它们的批准,此请求称为编号为M的提案的Accept请求。
注意:此时接收Accept请求的Acceptor集合,不一定是之前响应Prepare请求的Acceptor集合。
(5)Acceptor批准提案
根据Proposer生成提案的算法,一个Acceptor可能会收到来自Proposer的两种请求:编号为M的提案的Prepare请求和编号为M的提案的Accept请求。一个Acceptor会对Prepare请求做出响应的条件是:Acceptor可以在任何时候响应一个Prepare请求。一个Acceptor会对Accept请求做出响应的条件是:在不违背Acceptor现有承诺前提下,可响应任意Accept请求。
Acceptor可以忽略任何请求而不用担心破坏算法的安全性。因此,我们这里要讨论什么时候Acceptor可以响应一个请求。我们对Acceptor批准提案给出如下约束:
约束P1a:一个Acceptor只要尚未响应过任何编号大于M的Prepare请求,那么它就可以批准这个编号为M的提案。
可见,P1a包含了P1(一个Acceptor必须批准它收到的第一个提案)。
假设一个Acceptor收到一个编号为M的Prepare请求,在此之前它已经响应过编号大于M的Prepare请求。根据P1a,该Acceptor不可能再批准任何新的编号为M的提案,Acceptor也就没有必有对这个Prepare请求做出响应。因此,该Acceptor可以忽略编号为M的Prepare请求。
因此,每个Acceptor只需记住:它已批准提案的最大编号 + 它已响应Prepare请求的最大编号。这样即便Acceptor出现故障或者重启,也能保证满足P2c生成提案的规则。
而对于Proposer来说,只要它可以保证不会产生具有相同编号的提案,那么就可以丢弃任意的提案以及它所有的运行时状态信息。
(6)Paxos算法描述
结合Proposer和Acceptor对提案的处理逻辑;可以得出类似于两阶段提交的算法执行过程。
阶段一:(Prepare请求)
一.Proposer选择一个提案编号M,然后向半数以上的Acceptor发送编号为M的Prepare请求。
二.如果一个Acceptor收到一个编号为M的Prepare请求,且M大于该Acceptor已经响应过的所有Prepare请求的编号,那么它就会将它已经批准过的编号最大的提案作为响应反馈给Proposer,同时该Acceptor承诺不再批准任何编号小于M的提案。
阶段二:(Accept请求)
一.如果Proposer收到半数以上Acceptor,对其发出的编号为M的Prepare请求的响应,那么它就会发送一个针对[M, V]提案的Accept请求给半数以上的Acceptor。注意:V就是收到的响应中编号最大的提案的value。如果响应中不包含任何提案,那么V就由Proposer自己决定。
二.如果Acceptor收到一个针对[M, V]提案的Accept请求,只要该Acceptor没有对编号大于M的Prepare请求做出过响应,它就批准该提案。
(7)Learner学习被选定的value
Learner获取提案,有三种方案:
(8)如何保证Paxos算法的活性
一个极端的活锁场景:
(9)总结
二阶段提交协议解决了分布式事务的原子性问题,保证了分布式事务的多个参与者要么都执行成功,要么都执行失败。但是在二阶段解决部分分布式事务问题的同时,依然存在一些难以解决的诸如同步阻塞、无限期等待和脑裂等问题。
三阶段提交协议则是在二阶段提交协议的基础上,添加了PreCommit过程,从而避免了二阶段提交协议中的无限期等待问题。
Paxos算法引入过半的理念,也就是少数服从多数的原则。Paxos算法支持分布式节点角色之间的轮换,极大避免了分布式单点故障。因此Paxos算法既解决了无限期等待问题,也解决了脑裂问题。
整个Paxos算法就是想说明:每个Proposer生成的提案都去争取大多数Acceptor的批准。一旦有某个Proposer生成的提案[M0, V0]被大多数批准了,即便后面发现还有更多其他Proposer生成的提案[Mn, Vn]也被大多数Acceptor批准,那么这些提案的value值其实都是一样的,都为V0。因此就可以让每个Proposer都统一为一个value值为V0的提案,从而保证一致性。
7.Paxos协议的核心思想
(1)Paxos协议的核心思想
(2)Paxos协议的基本概念
(3)Paxos协议过程
(4)Paxos协议最终解决什么问题
(5)Paxos协议证明
(6)为什么要被多数派接受
(7)为什么需要做一个承诺
(8)为什么第二阶段A要从返回的提议中选择一个编号最大的
(9)Paxos协议的学习过程
(1)Paxos协议的核心思想
"与其预测未来,不如限制未来",这应该是Paxos协议的核心思想。Paxos协议本身是比较简单的,如何将Paxos协议工程化才是真正的难题。
(2)Paxos协议的基本概念
Proposal Value:提议的值
Proposal Number:提议编号,编号不能冲突
Proposal:提议 = 提议的值 + 提议编号
Proposer:提议发起者
Acceptor:提议接受者
Learner:提议学习者
说明一:协议中的Proposer有两个行为,一个是向Acceptor发Prepare请求,另一个是向Acceptor发Accept请求。
说明二:协议中的Acceptor则会根据协议规则,对Proposer的请求作出应答。
说明三:最后Learner可以根据Acceptor的状态,学习最终被确定的值。
为方便讨论,记[n, v]为提议编号为n、提议值为v的提议,记(m, [n, v])为承诺了Prepare(m)请求编号不再比m小,并接受过提议[n, v]。
(3)Paxos协议过程
一.第一阶段A
Proposer选择一个提议编号n,向所有的Acceptor广播Prepare(n)请求。
二.第一阶段B
Acceptor接收到Prepare(n)请求,若提议编号n比之前接收的Prepare请求都要大,则返回承诺将不会接收提议编号比n小的提议,并且带上之前Accept的提议中编号小于n的最大的提议,否则不予理会。
三.第二阶段A
Proposer得到了多数Acceptor的承诺后,如果没有发现有一个Acceptor接受过一个值,那么向所有的Acceptor发起自己的值和提议编号n。否则,从所有接受过的值中选择对应的提议编号最大的,作为提议的值,此时提议编号仍然为n。
四.第二阶段B
Acceptor接收到提议后,如果该提议编号不违反自己做过的承诺,则接受该提议。
需要注意的是,Proposer发出Prepare(n)请求后,得到多数派的应答。然后可以随便再选择一个多数派广播Accept请求,这时不一定要将Accept请求发给有应答的Acceptor。
五.协议过程总结
上面的图例中:首先P1广播了Prepare请求,但是给A3的Prepare请求丢失了。不过A1、A2成功返回了,即该Prepare请求得到多数派的应答。然后P1可以广播Accept请求,但是给A1的Accept请求丢失了。不过A2、A3成功接受了这个提议,因为这个提议被多数派(A2、A3形成多数派)接受,所以我们称被多数派接受的提议对应的值被Chosen。
情况一:如果三个Acceptor之前都没有接受过提议(即Accept请求),那么在第一阶段B中,就不用返回接受过的提议。
情况二:如果三个Acceptor之前接受过提议(即Accept请求),那么就需要在第一阶段B中,带上之前Accept的提议中编号小于n的最大的提议值,进行返回。
如下图示:Proposer广播Prepare请求之后,收到了A1和A2的应答,应答中携带了它们之前接受过的[n1, v1]和[n2, v2]。Proposer则根据n1、n2的大小关系,选择较大的那个提议对应的值。比如n1 > n2,那么就选择v1作为提议值,最后向Acceptor广播提议[n, v1]。
(4)Paxos协议最终解决什么问题
当一个提议被多数派接受后,这个提议对应的值会被Chosen(选定)。一旦有一个值被Chosen(选定),那么只要按照协议的规则继续交互。后续被Chosen的值都是同一个值,也就是保持了这个Chosen值的一致性。
(5)Paxos协议证明
上文就是基本Paxos协议的全部内容,其实是一个非常确定的数学问题。下面用数学语言表达,进而用严谨的数学语言加以证明。
一.Paxos原命题
如果一个提议[n0, v0]被大多数Acceptor接受,那么不存在提议[n1, v1]被大多数Acceptor接受,其中n0 < n1,v0 != v1。
二.Paxos原命题加强
如果一个提议[n0, v0]被大多数Acceptor接受,那么不存在Acceptor接受提议[n1, v1],其中n0 < n1,v0 != v1。
三.Paxos原命题进一步加强
如果一个提议[n0, v0]被大多数Acceptor接受,那么不存在Proposer发出提议[n1, v1],其中n0 < n1,v0 != v1。
如果"Paxos原命题进一步加强"成立,那么"Paxos原命题"显然成立。下面通过证明"Paxos原命题进一步加强",从而证明"Paxos原命题"。
四.归纳法证明
假设提议[m, v](简称提议m)被多数派接受,那么提议m到n(如果存在)对应的值都为v,其中n不小于m(m <= n)。
这里对n进行归纳假设,当n = m时,结论显然成立。设n = k时结论成立,即如果提议[m, v]被多数派接受,那么提议m到k对应的值都为v,其中k不小于m(m <= k)。
当n = k + 1时,若提议k + 1不存在,那么结论成立。若提议k + 1存在,对应的值为v1。
因为提议m已经被多数派接受,又k + 1的Prepare被多数派承诺并返回结果。基于两个多数派必有交集,易知提议k + 1的第一阶段B有带提议回来。那么v1是从返回的提议中选出来的,不妨设这个值是选自提议[t, v1]。
根据第二阶段B,因为t是返回的提议中编号最大的,所以t >= m。又由第一阶段A,知道t < n,即t < k + 1,也就是m <= t < k + 1。所以根据假设可知,提议m到k对应的值都为v。所以再根据m <= t < k + 1,可得出t对应的值就为v,即有v1 = v。因此由假设的n = k结论成立,可以推出n = k + 1成立。
于是对于任意的提议编号不小于m的提议n,对应的值都为v,所以命题成立。
五.反证法证明
要证明的是:如果一个提议[n0, v0]被大多数Acceptor接受,那么不存在Proposer发出提议[n1, v1],其中n0 < n1,v0 != v1。
假设存在,不妨设n1是满足条件的最小提议编号。
即存在提议[n1, v1],其中n0 < n1,v0 != v1。-----------------------(A)
那么提议n0、n0 + 1、n0 + 2、...、n1 - 1对应的值为v0。-------------(B)
由于存在提议[n1, v1],则说明大多数Acceptor已经接收n1的Prepare,并承诺将不会接受提议编号比n1小的提议。
又因为[n0, v0]被大多数Acceptor接受,所以存在一个Acceptor既对n1的Prepare进行了承诺,又接受了提议n0。
由协议的第二阶段B可知,这个Acceptor先接受了[n0, v0]。所以发出[n1, v1]提议的Proposer会从大多数的Acceptor返回中得知,至少某个编号不小于n0而且值为v0的提议已经被接受。----------(C)
由协议的第二阶段A知,该Proposer会从已经被接受的值中选择一个提议编号最大的值,作为提议的值。由(C)知可该提议编号不小于n0,由协议第二阶段B可知,该提议编号小于n1。于是由(B)知v1 == v0,与(A)矛盾。
所以命题成立。
(6)为什么要被多数派接受
因为两个多数派之间必有交集,所以Paxos协议一般是2F + 1个Acceptor。然后允许最多F个Acceptor停机,而保证协议依然能够正常进行,最终得到一个确定的值。
(7)为什么需要做一个承诺
可以保证第二阶段A中Proposer的选择不会受到未来变化的干扰。另外,对于一个Acceptor而言,这个承诺决定了:它回应提议编号较大的Prepare请求和接受提议编号较小的Accept请求的先后顺序。
(8)为什么第二阶段A要从返回的提议中选择一个编号最大的
这样选出来的提议编号一定不小于已经被多数派接受的提议编号,进而可以根据假设得到该提议编号对应的值是Chosen的那个值。
(9)Paxos协议的学习过程
如果一个提议被多数Acceptor接受,则这个提议对应的值被选定。一个简单直接的学习方法就是:获取所有Acceptor接受过的提议,然后看哪个提议被多数的Acceptor接受,那么该提议对应的值就是被选定的。
另外一个学习方法是:把Learner看作一个Proposer,根据协议流程,发起一个正常的提议,然后看这个提议是否被多数Acceptor接受。
注意:这里强调"一个提议被多数Acceptor接受",而不是"一个值被多数Acceptor接受"。
上图中,提议[3, v3],[5, v3]分别被B、C接受。虽然出现了v3被多数派接受,但不能说明v3被选定(Chosen)。只有提议[7, v1]被多数派(A和C组成)接受,才能说v1被选定,而这个选定的值随着协议继续进行不会改变。
8.ZAB算法简述
zk的ZAB协议是Paxos协议的一个精简版。ZAB协议即Zookeeper Atomic Broadcast,zk原子广播协议。ZAB协议是用来保证zk各个节点之间数据的一致性的。
ZAB协议包括以下特色:
特色一:Follower节点上全部的写请求都转发给Leader
特色二:写操作严格有序
特色三:使用改编的两阶段提交协议来保证各个节点的事务一致性,半数以上的参与者回复yes即可
广播模式:
广播模式就是指zk正常工作的模式。正常状况下,一个写入命令会通过以下步骤被执行:
步骤一:Leader从客户端或者Follower那里收到一个写请求
步骤二:Leader生成一个新的事务并为这个事务生成一个唯一的ZXID
步骤三:Leader将这个事务以Proposal形式发送给全部的Follower节点
步骤四:Follower节点将收到的事务请求加入队列,并发送ACK给Leader
步骤五:当Leader收到大多数Follower的ACK消息,会发送Commit请求
步骤六:当Follower收到Commit请求时,会判断该事务的ZXID是否是比队列中任何事务的ZXID都小来决定Commit
恢复模式:
当第一次启动集群时,先启动的过半机器中ZXID、myid最大的为Leader。当Leader故障时,zk集群进入恢复模式,此时zk集群不能对外提供服务。此时必须选出一个新的Leader完成数据一致后才能从新对外提供服务,zk官方宣称集群能够在200毫秒内选出一个新Leader。
正常模式下的几个步骤,每一个步骤都有可能由于Leader故障而中断,可是恢复过程只与Leader有没有Commit有关。
首先看前三个步骤,只做了一件事,把事务发送出去。若是事务没有发出去,全部Follower都没有收到这个事务,Leader故障了,全部的Follower都不知道这个事务的存在。
根据心跳检测机制,Follower发现Leader故障,需要重新选出一个Leader。此时会根据每一个节点ZXID来选择。谁的ZXID最大,表示谁的数据最新,就会被选举成新的Leader。若是ZXID都同样,那么就表示在Follower故障以前,全部的Follower节点数据一致,此时选择myid最大的节点成为新的Leader。
所以由于有一个固定的选举标准会加快选举流程,新的Leader选出来后,全部节点的数据同步一致后就能够对外提供服务。
假设新的Leader选出来以后,原来的Leader又恢复了,此时原来的Leader会自动成为Follower。
以前的事务即便发送给新的Leader,由于新的Leader已经开启了新的纪元,而原先的Leader中ZXID仍是旧的纪元,所以该事务就会被丢弃,而且该节点的ZXID也会更新成新的纪元。纪元就是标识当前Leader是第几任Leader,至关于改朝换代时候的年号。
若是在Leader故障以前已经Commit,zk会根据ZXID或者myid选出数据最新的那个Follower作为新的Leader。
新Leader会为Follower创建FIFO的队列,首先将自身有而Follower缺失的事务发送给该队列,然后再将这些事务的Commit命令发送给Follower,这样便保证了全部的Follower都保存了全部的事务数据。
ZAB协议确保那些已经在Leader提交的事务最终会被全部服务器提交,ZAB协议确保丢弃那些只在Leader提出或复制,但是没有提交的事务。