最近在大量看关于Zookeeper的博客,发现一篇讲解ZK实现很详细的博客,特此转载一下:

原博客地址:

http://my.oschina.net/zhengyang841117/blog/186600

1 Zookeeper介绍

Zookeeper是一个分布式的协调服务,为分布式应用程序提供synchronization、configuration maintenance、groups和nameing服务。

Zookeeper是一个有众多服务器节点组成的集群,这些节点中有一个主节点(leader),leader是通过leader selection自动地从服务器节点中选举出来。Zookeeper提供了一个类似于标准文件系统目录结构的hierarchal namespace(层次化的命名空间)。如下图所示,hierarchal namespace中的每一个节点都被称为 znode。Znode是组成hierarchal namespace的基本单位。在源码中对应于类DataNode,其维护着节点用户数据、父节点和子节点集合,以及本节点状态。用户可以在 hierarchal namespace中创建znode,将数据保存在znode中,并监听znode的状态变化,Zookeeper会保证client对znode的操作 是顺序一致性。

2 Zookeeper实现分析

Zookeeper服务器节点的实现可以分成两部分:一部分是处理与客户端交互,实现客户端对zookeeper的hierachal namespace的各种操作。另一部分是作为zab算法(paxos算法的zookeeper实现)的参与者(leader、follower、 observer三种角色的其中一种),实现具体的算法逻辑。

在这篇博客里,我们只讨论zookeeper服务器如何实现第一个部分功能。

Zookeeper服务器的hierachal namespace、znode、客户端与服务器连接、以及客户端可以监听服务器的znode状态的watch机制之间的元数据关系如下图所示:

Zookeeper使用Trie树 来实现了hierachal namespace,由PathTrie这个类来完成。为了实现从路径到znode的映射,zookeeper在内存中维护了一个znode的 hashmap,key为znode在hierachal namespace上的路径,value为znode对象,znode在zookeeper源码中由DataNode这个类实现。为了实现client监 听znode的状态变化,zookeeper将与客户端的连接和hierachal namespace的节点路径进行映射,WatchManager这个类就是用于维护这个映射关系的,其中NIOServerCnxn是 zookeeper服务器与client的一个socket连接;为了监听Znode的目录结构的变化和数据变化,zookeeper使用了两个 WatchManager,分别用来监听namespace的目录结构和数据的变化。

所有以上这些关系都封装在DateTree这个类中,DateTree的类图如下所示。

在paxos算法中有三种角色,分别是提案者,接受者和学习者。在zookeeper中有三种类型的节点,分别是leader、follower和 observer三种类型的节点,分别与paxos的三种角色相对应;需要注意的是zookeeper中还有一个learner的概念,这个 learner是paxos中的学习者,leader、follower和observer都是learner(即都是学习者,可以学习提案),但 observer节点除了是learner之外没有其他功能,也就是说observer只能学习已经批准的提案,而不会参与到提案的投票过程中。 observer这个角色的设定是为了保证提案选举的性能不会随着zookeeper集群规模扩大而降低(参与投票的节点越多,每一次投票花费的事件就越 多)。

在zookeeper中用LeaderZooKeeperServer,FollowerZooKeeperServer和

ObserverZooKeeperServer这三个类来实现三种类型的服务器节点。类图如下所示,为了简单起见没有详细给出成员变量和成员函数。

由于Zookeeper的服务器节点有多种类型,不同类型的服务器节点对client发过来的信令都有不同的处理流程,为了实现最大程度上的代码复 用,zookeeper采用了责任链的设计模式来实现各种类型的zookeeper节点。所以在每一个ZookeeperServer上都会维护一个 RequestProcessor责任链,来处理各个节点上的逻辑。

2.1 leaderZookeeperServer

Leader要完成以下几个事情:

1、接收客户端的request请求

2、将会修改同步数据的request请求 转化为proposal,并保存.

3、向所有的follower发送proposal。

4、接收follower的ack。

5、统计收到的ack,如果某一个proposal的ack超过了半数,那么向所有follower发送commit 信令,并向所有observer发送inform信令,执行这个proposal的动作。

6、leader自己执行已经被commit的proposal所对应的操作,并回复结果。

LeaderZookeeperServer的责任链如下面两图所示:

2.2 FollowerZooKeeperServer

Follower:主要负责批准或否决leader提出的proposal。Follower的主要逻辑处理如下:

1、 发现leader。

2、 建立与leader的连接。

3、 向leader注册。(leader activation)

4、 与leader进行同步。

5、 无限循环

---读取从leader处接收到的信令。

---处理从leader处接收到的信令。

A、 如果是PROPOSAL信令(写请求),将此信令投递到FollowerZooKeeperServer的synProcessor。主要作用是回复leader一个ack。

B、 如果是COMMIT信令,将此信令投递到FollowerZooKeeperServer的commitProcessor。最终执行FollowerZooKeeperServer的commit函数。

C、 如果是SYNC信令,将此信令投递到FollowerZooKeeperServer的commitProcessor。commitProcessor直接将此信令转发给FinalRequestProcessor,将sync信令带的内容写入持久层。

FollowZookeeperServer的责任链如下所示:

2.3 ObserverZooKeeperServer

observer:学习已经被commit的proposal的结果,然后执行相应的操作。Observer主要处理逻辑:

1、 发现leader。

2、 连接到leader上,建立TCP连接。

3、 与leader进行同步,同步leader上已经被commit的proposal。

4、 无限循环,读取接收到得信令,处理信令。

1、如果是syn信令,调用ObserverZooKeeperServer的syn函数,投递到commitProcessor中。

2、如果是info信令,同样调用ObserverZooKeeperServer的commit函数,投递到commitProcessor中。

OserverZookeeperServer的责任链基本上与follower的相同如下,只是commitProcessor调用的commit函数里的处理不同:

2.4 Zookeeper各节点交互

Zookeeper各个角色的信令交互图:

最后画了一张各个zookeeper server的信令交互图

第二篇:原博客地址:

http://my.oschina.net/zhengyang841117/blog/186676

一年多前学习zookeeper时做的笔记,主要是翻译自“ZooKeeper's atomic broadcast protocol:Theory and practice”,并添加了自己的一些理解,整理一下作为一篇博客贴出来,后续有时间会分析一下在zookeeper源码中,zab是如何实现的,以及 zab与paxos的区别。

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

1 Consistency Guarantees

Zookeeper不能保证强一致性,客户端能看到older数据。Zookeeper提供顺序一致性。

Zookeeper的一致性保证:

1、顺序一致性:客户端的更新通知是严格按照顺序进行发送。

2、原子性:更新操作要么成功要么失败,没有中间状态。

3、Single system image:不管客户端连接哪一个服务器,客户端看到的都是the same view of service。

4、Reliability:一旦一个更新成功,那么那就会被持久化,直到客户端用新的更新覆盖这个更新。

5、Timeliness:Zookeeper确保客户端在一定时间内(几十秒)完成或看到系统的数据更新。

那么zab是如何确保这些一致性相关的特点。

Zab的两个重要的要求如下:

1、 支持同时处理多个outstanding的客户端写操作。一个outstanding事务的含义是事务已经被提交但没有被commit。

2、 有效的从crash状态恢复过来。

Zookeeper能处理并发地处理多个客户端的outstanding 写请求,并且以FIFO顺序commit这些写操作。FIFO的特性对于zookeeper能够有效的从crash状态恢复过来也是至关重要的。

原始的paxos协议不能同时处理多个outstanding transaction,paxos不要求通信时的FIFO通道特性,paxos可以容忍消息丢失和重新排序。

在paxos中,从primary crash中恢复过来并保证事务的序列化的能力不是足够有效,而zab改进了这方面的能力,采用了一个事务ID来实现事务的totally order。

Zookeeper的性能要求如下:

1、 低延时(low latency)。

2、 Good throughput。高吞吐量。

3、 Smooth failure handling。容错。

在这种情况下,为了能有效地更新一个new primary的应用程序状态,在zab中new primary会被期望拥有最高事务ID的进程,整个集群可以通过从new primary中拷贝事务,从而所有数据副本都可以达到一个一致性。

而在paxos,没有采用类似zab的序列号,所以一个新的primary需要执行paxos算法的第一阶段,以便于获取到所有primary没有学习到值。

2 ZAB协议和流程介绍

Zab协议有四个阶段,如下:

1、阶段0:Leader election

2、阶段1:Discovery(或者epoch establish)

3、阶段2:Synchronization(或者sync with followers)

4、阶段3:Broadcast

在Zab协议的实现时,合并为三个阶段:

1、 Fast Leader Election

2、 Recovery Phase

3、 Broadcast Phase

在实现中将discovery和synchronization这两个phase合并成了broadcast phase。

ZAB的流程图如下所示:

CEPOCH = Follower sends its last promise to the prospective leader

NEWEPOCH = Leader proposes a new epoch e'

ACK-E = Follower acknowledges the new epoch proposal

NEWLEADER = Prospective leader proposes itself as the new leader of epoch e'

ACK-LD = Follower acknowledges the new leader proposal

COMMIT-LD = Commit new leader proposal

PROPOSE = Leader proposes a new transaction

ACK = Follower acknowledges leader proosal

COMMIT = Leader commits proposal

3 Leader election

3.1 leader election后置条件

Leader election可能有多种方式,但在这里我们只分析一种,fast leader election。

Leader election后置条件:

1、条件:Leader election这个过程必须保证选举出来的leader能看到所有历史的commited transactions。

2、原因:这个后置条件是为了确保在后续recovery phase步骤中zookeeper replicas的一致性。它是防止follower中包含leader中没有的committed transaction,而且在recovery phase中只有leader向follower和observer同步,follower不会向leader同步,如果出现这种情况,那么 zookeeper的replicas就出现了不一致的情况。

所以为了达到这个后置条件,leader election需要选择出一个拥有highest lastZxid的leader。

那么fast leader election是如何选择出一个拥有highest lastZxid的leader?

3.2 Fast leader election介绍

在进行fast leader election过程中,为了选举出一个拥有highest lastZxid的leader(能看到最新的历史committed transaction),处于election状态的peer servers会对其他peer server进行表决。Peer server会交换他们的vote(选举)的通知。同时当peer server发现一个拥有recent history的peer server(peer server拥有higher history Zxid),peer server会更新其自身的vote。当选举出一个leader后,然后进入recovery phase,fast leader election就结束了,假如vote选举出来leader就是peer server自身,那么peer server变成leading状态(fast leader election过程中,peer server本身的状态是following),其他的peer server则进入following状态。如果后续的recovery phase和broadcast phase发生任何失败的情况,那么peer server都会回到election状态,重新启动fast leader election。

3.3 Epoch number

Epoch是用于区分每一个round,每一次建立一个新的leader-follower关系,都会有一个唯一的epoch值去标识。就好像皇帝登基必须得有一个年号,与之前或之后的皇帝进行区分。

Epoch在两个过程中用到:1、leader election时。2、recovery过程(新建立一个leader-follower关系)。

1、过程1:每一个fast leader election开始时epoch的值都为0,epoch的值会在fast leader election过程中进行更新。

个人理解每个zookeeper节点刚启动时没有leader-follower关系视图,那么它就会认为自己是leader,然后发起 leader electoin,那么这个leader election的epoch值为0;在leader election过程中,将epoch更新到currepoch值(其他peer server中的最高的epoch)。使用epoch number来区分不同的fast leader election过程。就好像你想当皇帝,定了一个年号发起登基过程,如果当前有其他皇帝存在,且他的年号比你的年号更新,那么你就得更新年号,重新发起 登基,谁支持的人多谁就是皇帝;如果没有其他皇帝存在,但有其他人也在登基,那么大家就一起比比,看谁的年号更新,看谁的资格更老(同样的 epoch,vote值越大越优先),那么选举谁当皇帝。

2、过程2:在一个faster leader election结束后,新产生的leader会获取epoch,其值为lastest history zxid的高32位,然后对epoch自增,然后用新的epoch值作为新zxid的高32,zxid的低32位为0。一旦当上皇帝后,就发布一个新的年 号。

这里有矛盾的地方:

两个过程的epoch是否是同一个?过程1的epoch是不会持久化的。过程2中因为zxid是持久化的,那么相当于epoch也是持久化的。所以不理解。

3.4 选取出highest zxid的leader

为了能选举出highest zxid的leader,那么就需要对vote进行比较。

对于peer server集合 PSET = {p1, p2, p3, …., pn},其中{1, 2, 3, …. , n }是peer server的ID.

那么Pi的vote可以用pair(Zi, i)表示,Zi是Pi的highest zxid,也是lastest zxid。

那么两个vote比较大小的准则是:

(Zi, i) >= (Zj, j) : Zi > Zj 或者( Zi = Zj && i >= j )

每一个peer server都有一个唯一的ID,且都知道其replicas中保存的latest zxid,那么所有的peer就会以一定顺序进行排序。

3.5 Fast leader election持久化

在fast leader election过程中,不会对任何数据进行持久化,不会把过程中产生的值写入到disk中。包括epoch number和ID但在fast leader election会使用已经持久化的latest zxid。

3.6 Fast leader election过程和伪码

进行Fast leader election的先决条件:

1、 每个peer server都知道其他peer的ip地址,并知道peer server的总数。

2、 每个peer server一开始都是发起一个vote,选取自己为leader。向其他所有的peer server发送vote的notification,并等待回复。

3、 根据peer server的状态处理vote notification或则notifincation的回复.

如果peer server处于election状态,那么peer server会收到其他peer server的vote,如果收到的vote值更大,那么peer server会更新其vote。

如果peer server不处于election状态,那么peer server会更新其所看到的leader-follower关系。

不管哪种情况下,当peer server检测到大部分peers持有相同的vote时,那么它会返回

Fast leader election逻辑伪代码

主要有两个逻辑分支:

1、正常过程,vote的notification的回复的peer server的状态为election

2、另外过程,vote的notification的回复的peer server的状态为leading/following

执行leader election的情况较为复杂,可能是一个服务器节点新加入到zookeeper集群中。也可能是zookeeper集群刚启动,大家都处于leader election状态。以上两个逻辑分支能处理这些情况。

***初始化vote和peer server状态***

1 Peer P:

2 timeout <---T0 // use some reasonable timeout value

3 ReceivedVotes <--- 0; OutOfElection <--- 0; // key-value mappings where keys are server ids

4 P:state <--- election;  P:vote <---(P:lastZxid; P:id);  P:round <--- P:round + 1

1-4是初始化过程,设置超时时间,receivedVotes是收到的vote noficaton回复。

进入election状态,根据lastZxid和ServerID生成一个vote,vote的epoch自增。

ReceivedVotes作为一个结果集合,在收到所有vote后,进行表决。OutOfElection用于保存状态为leading/folling的rspvote,用于表决先存在的leader/follower是否有效。

5 Send notification (P:vote, P:id, P:state, P:round) to all peers

向所有的peer server发送notification,一个notification包括vote,id,peer state,和vote的epoch number。

***开始接收notification回复的循环处理***

6 while P:state = election do

7     n <---(null if P:queue = 0; for timeout milliseconds, otherwise pop from P:queue)

8     if n = null then

9          Send notification (P:vote, P:id, P:state, P:round) to all peers

10        timeout <---(2* timeout), unless a predefined upper bound has been reached

8-10是当notification回复为空时,有两种情况,一种是信令发送出去回复超时,第二种是没有建立于peer server的连接.

如果是第一种情况,那么重新发送notification;如果是第二种情况,那么建立与peer server的tcp连接.

11    else if n:state = election then //当nofication回复不为空,且peer server的状态也是election时

12         if n:round > P:round then

13               P:round <--- n:round

14               ReceivedVotes <---0

15               if n:vote > (P:lastZxid; P:id) then P:voteßn:vote

16               else P:vote <---(P:lastZxid; P:id)

17               Send notification (P:vote, P:id, P:state, P:round) to all peers

这个逻辑分支是notification回复中resvote的epoch要大于vote

的epoch(说明回复中的peer vote的zxid > vote的zxid),那么vote失效了,需要更新vote,比较回复中的两个vote值的大小,选择值大的vote,然后重新发送notification。

18         else if n:round = P:round and n:vote > P:vote then

19              P:vote <--- n:vote

20              Send notification (P:vote, P:id, P:state, P:round) to all peers

当回复中的rspvote的epoch等于vote的epoch,但rspvote > vote,那么更新vote信息

然后重新将vote向所有的peer server发送。

21          else if n:round < P:round then goto line 6

Resvote的epoch小于vote的epoch,那么这个回复是无效的,

直接忽略,继续下一个循环。

22          Put(ReceivedVotes(n:id); n:vote; n:round)

将rspvote放入到ReceivedVotes中。

23         if  ReceivedVotes = SizeEnsemble then

24                DeduceLeader(P.vote.id);  return P.vote

如果已经收到了所有peer server的vote,如果vote中的leaderID == currentPeer本身,

那么currPeer为leader,结束并返回此次vote结果。

25         else if P.vote has a quorum in ReceivedVotes

and there are no new notifications within T0 milliseconds then

26                DeduceLeader(P.vote.id);  return P.vote

如果收到超过半数peer server的vote,那么vote中的leaderID == currentPeer本身,

那么currPeer为leader,结束并返回此次vote结果.

27          end

逻辑分支1总结:

如果rspvote中epoch > vote epoch,更新epoch和vote后重新发起vote

如果rspvote中epoch < vote epoch,无效rspvote

其他,都保存在结果集合中,如果有rspvote>vote,那么将vote更新到rspvote;等待所有rspvote都收到,那么vote的 值应该为结果集合中最大值,如果结果集合超过半数,那么此次vote生效,leader为vote中的serverID。如果serverID为本身的 serverID,那么currpeer的状态为leader否则为follower

28    else // state of n is LEADING or FOLLOWING

当rspvote的状态为following或leading,说明vote之外已经存在了一个leader,那么此段逻辑主要是分成两部分:一部分是vote的表决;另一部分是vote之外的leader/follower表决.

29         if n:round = P:round then

30             Put(ReceivedVotes(n.id); n:vote; n:round)

31             if n:state = LEADING then

32                 DeduceLeader(n:vote:id); return n:vote

33             else if n:vote:id = P:id and n:vote has a quorum in ReceivedVotes then

34                 DeduceLeader(n:vote:id); return n:vote

35             else if n:vote has a quorum in ReceivedVotes and the voted peer n:vote:id is in

state LEADING and n:vote:id 2 OutOfElection then

36                  DeduceLeader(n:vote:id); return n:vote

37             end

38         end

以上部分是vote的表决,以上的逻辑跟代码中不符合,代码中的逻辑是:

如果rspvote的epoch==vote的epoch,放入到receivedVots中,如果rspvote的状态是leader

且集合中的rspvote超过半数,那么vote的表决的leader就是rspvote的leader。

39         Put(OutOfElection(n:id); n:vote; n:round)

40         if n:vote:id = P:id and n:vote has a quorum in OutOfElection then

41             P:round <--- n:round

42             DeduceLeader(n:vote:id); return n:vote

43         else if n:vote has a quorum in OutOfElection and the voted peer n:vote:id is in state

LEADING and n:vote:id 2 OutOfElection then

44             P:round <--- n:round

45             DeduceLeader(n:vote:id); return n:vote

46          end

以上部分是对vote之外的leader/follower进行表决,OutOfElection是用来存放状态为leader/follow的 rspstate,如果OutOfElection的rspvote超过半数,那么说明election之外的leader./follow是有效地,

47  end

逻辑分支2总结:这部分是考虑到可能有部分peer server维持leader/follower的状态,部分peer server处于election状态,如果维持leader/follower状态的peer server数据过半,那么leader/follower就是有效地。或者vote的epoch等于leader的epoch,那么如果有半数以上的 rspvote,那么当前的leader/follower也是有效的。

4 Discovery and synchronization

在broadcast阶段,zookeeper集群必须有一个leader peer,zookeeper集群是primary/backup模式,那么leader就是primary。Discovery和 synchronization这两个阶段的作用就是将全部的zookeeper节点带入到一个最终一致的状态,特别是当从crash中恢复时。这两个阶 段组成了zab的recovery部分,对于允许多个独立事务的情况下,保证事务的顺序起着关键作用。

不管在discovery、synchronization还是broadcast,一旦发生错误,那么都可以回到leader election过程。

用户如果需要使用zookeeper服务,那么必须连接一个zookeeper节点。用户向连接的服务器提交写操作,然后zab协议层会执行一个 broadcast;假如用户向follower提交写操作,那么follower会把写操作发送给leader;如果leader收到写操 作,leader会执行,然后向所有follower扩散这个写操作对应的数据更新。读操作可以由与用户相连接的zookeeper节点直接完成。用户可 以通过发送sync命令保证数据副本的更新。

在zab协议中,zxid(transaction identifiers)对于实现顺序一致性十分关键。在zookeeper中事务可以用(v, z)表示,v是新状态(znode),z则是zxid,一个identifier。那么一个zxid也是一个pair(e, c),e是一个primary Pe(可以理解为leader)的epoch number,c是一个整数值,作为计数器使用。Primary每产生一个新的事务,那么计数器c就会+1。

当一个新的epoch开始时,一个新的leader会被激活,此时c会被设置为0,e会在前一个epoch的值上+1。

在代码实现中e是zxid的高32位,c是zxid的低32位。

以下四个变量构成了一个peer的持久化状态:

1、History:已经被接受的事务提案(transaction proposal)。

2、acceptedEpoch:最近收到的NEWEPOCH信令中的epoch number。

3、currentEpoch:最近收到的NEWLEADER信令中的epoch number。

4、lastZxid:history中的最近的zxid。

5 Discovery过程

在这个阶段,followers会跟他们的未来预期中的leader进行通信,准leader会收集accepted follower(已经建立连接的)的latest transactions,这个阶段的目的是发现quorum peer server中的highest histroy transaction,然后建立一个新的epoch,这样就可以防止previous leader不会commit 新的proposals(因为previous leader的epoch已经过期了)。

在discovery阶段的开始,一个follower peer会建立于准leader的leader-follower connection。

Follower同时只是连接一个leader。假如一个peer P不是leading状态,其他peer会考虑p是一个准leader,任何其他leader-follower连接都会被p拒绝;同样leader- follower连接的拒绝或其他的failure能将follower重新带入到leader election状态。

1 Follower F:

2 Send the message FOLLOWERINFO(F:acceptedEpoch) to L

3   upon receiving NEWEPOCH(e0) from L do

4      if e0 > F:acceptedEpoch then

5          F:acceptedEpoch <--- e0 // stored to non-volatile memory

6          Send ACKEPOCH(F:currentEpoch; F:history; F:lastZxid) to L

7           goto Phase 2

8      else if e0 < F:acceptedEpoch then

9           F:state <--- election and goto Phase 0 (leader election)

10     end

11 end

这个过程是follower端,follower向准leader发送FOLLOWERINFO信令,告诉leader自己的信息,最重要的就是把 accepted epoch发送给leader。然后接收leader的NEWLEADER信令,NEWLEADER信令中带有new epoch(这个epoch表示这这一轮过程,每一次建立leader-follower关系,都会有一个新的epoch来唯一标识,与previous leader-follower进行区分)。Follower检查这个new epoch是否有效,如果有效,follower更新自身的epoch并回复一个ACKEPOCH,上报当前follower的状态,进入下一个阶段。如 果无效,那么follower会重新跳到leader electoin阶段。

12 Leader L:

13 upon receiving FOLLOWERINFO(e) messages from a quorum Q of connected followers do

14      Make epoch number e0 such that e0 > e for all e received through FOLLOWERINFO(e)

15      Propose NEWEPOCH(e0) to all followers in Q

16 end

17 upon receiving ACKEPOCH from all followers in Q do

18      Find the follower f in Q such that for all f0 2 Q n ffg:

19          either f0:currentEpoch < f:currentEpoch

20          or (f0:currentEpoch = f:currentEpoch) ^ (f0:lastZxid _z f:lastZxid)

21      L:history <--- f:history  // stored to non-volatile memory

22      goto Phase 2

23 end

这个是leader端的recovery过程,leader会生产一个new epoch,首先接收所有follower的epoch,确定new epoch要大于所有的follower epoch。然后向所有follower发送NEWEPOCH信令,将new epoch下发到所有的follower中。

等待follower的ACKEPOCH回复,如果所有的follower的currEpoch和zxid都小于等于leader的currEpoch和zxid,那么进入下一个过程。

6 Synchronization过程

这个过程是将follower的数据副本与准leader的历史数据进行同步,使得zookeeper集群的数据处于一致的状态。同步的方向是准 leader向follower同步。同步的过程如下:leader与follower进行通信,发送NEWLEADER信令,带有历史事务的 highest zxid;follower收到这些信令后,决定是否更新历史事务,然后响应leader。当leader看到quorum follower的响应后,就会向它们发送commit信令。在这之后leader就建立完成了。

1 Leader L:

2 Send the message NEWLEADER(e0;L:history) to all followers in Q

3 upon receiving ACKNEWLEADER messages from some quorum of followers do

4      Send a COMMIT message to all followers

5      goto Phase 3

6 end

这是leader端的过程,发送NEWLEADER,然后接受响应,最后发送commit,至此leader建立完毕。

7 Follower F:

8 upon receiving NEWLEADER(e0;H) from L do

9      if F:acceptedEpoch = e0 then

10         atomically

11             F:currentEpoch <--- e0 // stored to non-volatile memory

12             for each (v; z) in H, in order of zxids, do

13                  Accept the proposal (e0; (v; z))

14             end

15             F:history <---H // stored to non-volatile memory

16         end

17         Send an ACKNEWLEADER(e0;H) to L

18     else

19          F:state <--- election and goto Phase 0

20     end

21 end

22 upon receiving COMMIT from L do

23      for each outstanding transaction (v; z) in F:history, in order of zxids, do

24          Deliver (v; z)

25      end

26      goto Phase 3

27 end

这是follower端的流程,先是收到NEWLEADER信令,然后原子地更新epoch和历史事务,发送ACKNEWLEADER信令响应leader;然后等待commit信令,收到commit信令后进行处理,进入下一个阶段。

7 代码实现的Recovery phase

在实现discovery和synchronization时,没有严格分成两个阶段进行实现,在实现时进行了一些优化,合并成一个阶段实现,那么 这个阶段就是recovery phase;recovery阶段就是将所有的zookeeper集群的数据副本进入到最终一致性地状态中,且建立出一个具有最高highest zxid的leader。

在实现中,第0阶段的fast leader election与第一阶段discovery紧密结合在一起,faster leader election在实现时做了一个优化,它会选择出一个most up-to-date的history(个人理解就是选择出一个具有最新的commit事务的peer server),那么这样的一个leader被选举出来后,在第一阶段就不需要去与followers通信去发现latest history。

那么既然在fast leader election中包括了discovery阶段的责任,那么这个discovery阶段就可以被忽略,所以在实现时就将discovery和 synchornization阶段合并成一个recovery阶段。这个阶段是在fast leader election之后,且认为leader拥有lastest history。

伪码:

1 Leader L:

2 L:lastZxid <--- (L:lastZxid:epoch + 1; 0)

3 upon receiving FOLLOWERINFO(f:lastZxid) message from a follower f do

4      Send NEWLEADER(L:lastZxid) to f

5      if f:lastZxid  <=  L:history:lastCommittedZxid then

6          if f:lastZxid  <=  L:history:oldThreshold then

7              Send a SNAP message with a snapshot of the whole database of L

8          else

9              Send a DIFF({committed transaction (v; z) in L:history : f:lastZxid < z})

10        end

11     else

12         Send a TRUNC(L:history:lastCommittedZxid) message to f

13     end

14 end

15 upon receiving ACKNEWLEADER messages from some quorum of followers do

16     goto Phase 3 // Algorithm 3

17 end

以上是leader端的流程,先生存一个新的zxid和epoch,接收follower的FOLLOWERINFO信令(包含follower的 lastzxid),然后向follower发送NEWLEADER(包含leader的zxid)。然后根据FOLLOWERINFO中带有的 lastzxid对follower进行更新。分成三种情况…….

History.lastCommittedZxid是最新committed的历史事务。History.oldThreshold是太久的历史提案,比leader上一次snapshot的时间还久。见2.6.2关于TRUNC的说明。

第一种情况是TRUNC,follower丢弃从leader.latestZxid到follower.lasterZxid之间的提案。

第二种情况是DIFF,follower接收新的提案从follower.lasterZxid到leader.lasterZxid之间的新提案。

第三种情况是SNAP,follower中的提案太旧,leader将snap更新到follower上。

18 Follower F:

19 Connect to its prospective leader L

20 Send the message FOLLOWERINFO(F:lastZxid) to L

21 upon L denies connection do

22     F:state <--- election and goto Phase 0

23 end

24 upon receiving NEWLEADER(newLeaderZxid) from L do

25     if newLeaderZxid:epoch < F:lastZxid:epoch then

26         F:state <--- election and goto Phase 0

27     end

28     upon receiving a SNAP, DIFF, or TRUNC message do

29         if got TRUNC(lastCommittedZxid) then

30             Abort all proposals from lastCommittedZxid to F:lastZxid

31         else if got DIFF(H) then

32             Accept all proposals in H, in order of zxids, then commit all

33         else if got SNAP then

34             Copy the snapshot received to the database, and commit the changes

35         end

36         Send ACKNEWLEADER

37         goto Phase 3 // Algorithm 3

38    end

39 end

以上是follower的流程,首先是向leader连接,然后发送FOLLOWERINFO信令,如果leader拒绝连接,那么 follower重新回到leader election阶段。接收NEWLEADER信令,如果信令中带有的epoch无效(小于follower的epoch),那么follower重新回 到leader election状态。

然后接收SNAP/DIFF/TRUNC信令,同步数据副本和zxid,最后回复ACKNEWLEADER信令。进入到下一个阶段。

这个同步的目的是让所有数据副本都进入一个最终一致性状态。为了达到这个目的,任何副本中的committed transactions必须以同样一种顺序,甚至已经被提交的transaction但没有被任何一个peer节点committ的事务必须被抛弃。 SNAP和DIFF用于保证各个副本中的committed事务的顺序一致性;而TRUNC用于处理已经被提交但没有被committed的事务。

8 Broadcast phase

Zookeeper peer之间的双向通道使用TCP连接实现,TCP通信的FIFO序列化特性对于实现broadcast协议至关重要。

假如没有发生崩溃,那么peers会一直停留在broadcast阶段。第三阶段中只能有一个leader。

Broadcast的过程是leader与follower之间的一个两阶段的提交过程(two-phase commit)

1、 leader与follower的通讯通道(communication channel)是一个FIFO,所有都是是按顺序处理。

2、 leader收到一个request后,会生成一个propose。然后执行两阶段提交.

Broadcast的伪码和流程

1 Leader L:

2 upon receiving a write request v do

3     Propose (e0; (v; z)) to all followers in Q, where z = (e0; c), such that z succeeds all zxid

values previously broadcast in e0 (c is the previous zxid's counter plus an increment of one)

4 end

5 upon receiving ACK((e0; (v; z))) from a quorum of followers do

6     Send COMMIT(e0; (v; z)) to all followers

7 end

以上是leader处理的两阶段提交的流程:首先leader受到写请求v,然后生成一个提案(e,(v,z)),向所有follower发送此提 案的内容,然后等待follower的ack;如果ack超过半数,那么提案成立。向所有follower下发commit提案的命令。

8 // Reaction to an incoming new follower:

9 upon receiving FOLLOWERINFO(e) from some follower f do

10     Send NEWEPOCH(e0) to f

11     Send NEWLEADER(e0;L:history) to f

12 end

13 upon receiving ACKNEWLEADER from follower f do

14     Send a COMMIT message to f

15     Q <--- Q 并集 {f}

16 end

以上是一个新follower加入leader的流程:首先leader收到FOLLOWERINFO信令,然后向new follower发送NEWEPOCH信令,再发送NEWLEADER信令给new follower;等待new follower的ACKNEWLEADER,最后发送commit,至此new follower就加入到了集群中。

17 Follower F:

18 if F is leading then Invokes ready(e0)

19 upon receiving proposal (e0; (v; z)) from L do

20     Append proposal (e0; (v; z)) to F:history

21     Send ACK((e0; (v; z))) to L

22 end

23 upon receiving COMMIT(e0; (v; z)) from L do

24     while there is some outstanding transaction (v0; z0) in F:history such that z0 < z do

25         Do nothing (wait)

26     end

27     Commit (deliver) transaction (v; z)

28 end

这是follower的broadcast流程:接收到leader的提案,然后将提案写入到history中,然后发送响应。等待leader的commit信令,收到后执行commit 提案。

9 Zab所存在的问题

9.1 acceptedEpoch和currentEpoch的作用

在recovery开始阶段,准leader甚至在与大部分follower成功建立连接之前就增加其epoch(包括在lastZxid内)值。 因为在recovery阶段,follower在发现其epoch值要比准leader大时,会返回到leader election阶段。那么当准leader失去leader地位,并成为previous leader(其epoch比准leader要小1)的一个follower,那么准leader会发现previous leader的epoch值比其要小,那么它会返回到leader election阶段。这个现象会导致此peer一直在recovery阶段和leader election阶段之间循环。

所以使用lastZxid来存储epoch number,没有对一个tried epoch(个人理解是一个准leader在尝试成为leader时使用的epoch)和一个joined epoch(一个成功的leader所使用的epoch)进行区分。使用acceptedEpoch和currentEpoch的目的就是在于防止此类问 题的发生。

9.2 Abandon follower proposal

假设一个集合{p1, p2, p3},所有的peers都处于broadcast阶段,且都已经同步到了最新的committed事务,事务的ID是(e= 1, c= 3),p1为leader;一个新的提案,事务ID为(1, 4)已经被leader p1发出,但在p2和p3收到事务之前,p1就已经发生了崩溃(比如已经放到socket缓存区中),那么{p2, p3}会重新回到leader election,并选举出一个新的leader。当p1恢复正常了,此时p2已经成为了leader;那么根据fast leader election,在recovery阶段p2会将epoch设置为2(p2.latestZxid = (2, 0)),那么在broadcast阶段,已经新的提案已经被quorum接收和commit,它的zxid为(2, 1)。在这个时候leader p2的history.lastCommittedZxid = (2, 1),并且p2的history.OlderThreshold = (1, 1);那么p1重新启动后,p1会执行fast leader election,然后发现其他peer已经建立leader-follower关系,且p2是leader,那么p1会向发送 FOLLOWERINFO(p1.latestZxid = (1, 4))。

在这种情况下,

p1.lastestZxid(1,4) < p2.history.lastCommittedZxid(2, 1)

&& p2.history.oldThreshold(1, 1)< p1.lastestZxid (1, 4),那么这种情况下leader p2需要向p1发送TRUNC信令,让follower放弃uncommitted proposal(1, 4)。

转)ZooKeeper的实现分析的更多相关文章

  1. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  2. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  3. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  4. Zookeeper 源码分析-启动

    Zookeeper 源码分析-启动 博客分类: Zookeeper   本文主要介绍了zookeeper启动的过程 运行zkServer.sh start命令可以启动zookeeper.入口的main ...

  5. Zookeeper ZAB 协议分析[转]

    写在开始:这是我找到一篇比较好的博客,转载到这来进行备份原文参考: Zookeeper ZAB 协议分析 前言 ZAB 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播 ...

  6. 分布式系统的一致性级别划分及Zookeeper一致性级别分析

    最近在研究分布式系统的一些理论概念,例如关于分布式系统一致性的讨论,看了一些文章我有一些不解.大多数对分布式系统一致性的划分是将其分为三类:强一致性,顺序一致性以及弱一致性.强一致性(Strict C ...

  7. zookeeper源码分析之二客户端启动

    ZooKeeper Client Library提供了丰富直观的API供用户程序使用,下面是一些常用的API: create(path, data, flags): 创建一个ZNode, path是其 ...

  8. zookeeper源码分析二FASTLEADER选举算法

    如何在zookeeper集群中选举出一个leader,zookeeper使用了三种算法,具体使用哪种算法,在配置文件中是可以配置的,对应的配置项是"electionAlg",其中1 ...

  9. zookeeper源码分析三LEADER与FOLLOWER同步数据流程

    根据二)中的分析,如果一台zookeeper服务器成为集群中的leader,那么一定是当前所有服务器中保存数据最多的服务器,所以在这台服务器成为leader之后,首先要做的事情就是与集群中的其它服务器 ...

  10. zookeeper源码分析(一) 工作原理

    来自:http://www.codedump.info/?p=207 阅读zookeeper代码一段时间(注:是很长一段时间,断断续续得有半年了吧?)之后,我要开始将一些积累下来的东西写下来了,鉴于我 ...

随机推荐

  1. HDTV(1920x1080)码率和视频质量关系的研究 2 (实验结果)

    上一篇文章中介绍了实验的准备工作, HDTV(1920x1080)码率和视频质量关系的研究 1 (前期准备) 本文介绍一下实验的结果. 首先来看一下主观评价的试验结果: 从实验结果来看,可以得出以下结 ...

  2. C# BarCodeToHTML条码生成类

    来自:http://www.sufeinet.com/forum.php?mod=viewthread&tid=656&extra=page%3D1%26filter%3Dtypeid ...

  3. nodejs书籍

    http://product.dangdang.com/23371791.html#catalog https://www.byvoid.com/project/node http://www.ama ...

  4. OpenCV实现仿射变换

    什么是仿射变换?¶ 一个任意的仿射变换都能表示为 乘以一个矩阵 (线性变换) 接着再 加上一个向量 (平移). 综上所述, 我们能够用仿射变换来表示: 旋转 (线性变换) 平移 (向量加) 缩放操作 ...

  5. 【LaTeX排版】LaTeX论文排版<一>

    本文及接下来的几篇文章主要讲关于毕设论文的排版. 1.论文的整体构架     学校规定论文字数不得少于15000:说明论文属于中篇论文.一般来说,中长篇论文采用book文类,短篇论文采用article ...

  6. Junit指定测试执行顺序

    原文链接: Test execution order原文日期: 2012年12月06日翻译日期: 2014年7月2日翻译人员: 铁锚说明: Junit4.11版本及以后才支持,建议升级到最新版本.按照 ...

  7. 高通Android display架构分析

    目录(?)[-] Kernel Space Display架构介绍 函数和数据结构介绍 函数和数据结构介绍 函数和数据结构介绍 数据流分析 初始化过程分析 User Space display接口 K ...

  8. ZooKeeper leader election

    Paxos是分布式应用中解决同步问题的核心.作为应用研发工程师,我们总是倾向于使用一种相对简洁的方式实现复杂的算法.ZooKeeper leader election实现就是一个非常好的参考. 其实现 ...

  9. ANSI控制码的说明

    例如: echo -ne "\33[32m" 可以将字符的显示颜色改为绿色 echo -ne "\33[3;1H" 可以将光标移到第3行第1列处 具体的摘抄一些 ...

  10. profile bashrc bash_profile之间的区别和联系

    profile bashrc bash_profile之间的区别和联系 博客分类: Linux   执行顺序为:/etc/profile -> (~/.bash_profile | ~/.bas ...