zk基础—1.一致性原理和算法二

大纲

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提出或复制,但是没有提交的事务。

相关推荐
寒士obj4 天前
分布式组件【ZooKeeper】
微服务·zookeeper
笨蛋少年派4 天前
zookeeper简介
分布式·zookeeper·云原生
007php0078 天前
百度面试题解析:Zookeeper、ArrayList、生产者消费者模型及多线程(二)
java·分布式·zookeeper·云原生·职场和发展·eureka·java-zookeeper
坐吃山猪9 天前
zk02-知识演进
运维·zookeeper·debian
yumgpkpm9 天前
华为鲲鹏 Aarch64 环境下多 Oracle 数据库汇聚操作指南 CMP(类 Cloudera CDP 7.3)
大数据·hive·hadoop·elasticsearch·zookeeper·big data·cloudera
小醉你真好10 天前
16、Docker Compose 安装Kafka(含Zookeeper)
docker·zookeeper·kafka
yumgpkpm11 天前
CMP (类ClouderaCDP7.3(404次编译) )华为鲲鹏Aarch64(ARM)信创环境多个mysql数据库汇聚的操作指南
大数据·hive·hadoop·zookeeper·big data·cloudera
yumgpkpm14 天前
大数据综合管理平台(CMP)(类Cloudera CDP7.3)有哪些核心功能?
hive·hadoop·elasticsearch·zookeeper·big data
回家路上绕了弯17 天前
深入 Zookeeper 数据模型:树形 ZNode 结构的设计与实践
后端·zookeeper
90919322117 天前
SQL关键词标签在数据分析中的应用与实践
zookeeper