<p>一年多前学习zookeeper时作的笔记,主要是翻译自“ZooKeeper's atomic broadcast protocol:Theory and practice”,并添加了本身的一些理解,整理一下做为一篇博客贴出来,后续有时间会分析一下在zookeeper源码中,zab是如何实现的,以及zab与paxos的区别。</p> <p>--------------------------------------------------------------------------</p> <h3>1 Consistency Guarantees</h3> <p>Zookeeper不能保证强一致性,客户端能看到older数据。Zookeeper提供顺序一致性。</p> <p>Zookeeper的一致性保证:</p> <p>一、顺序一致性:客户端的更新通知是严格按照顺序进行发送。</p> <p>二、原子性:更新操做要么成功要么失败,没有中间状态。</p> <p>三、Single system image:无论客户端链接哪个服务器,客户端看到的都是the same view of service。</p> <p>四、Reliability:一旦一个更新成功,那么那就会被持久化,直到客户端用新的更新覆盖这个更新。</p> <p>五、Timeliness:Zookeeper确保客户端在必定时间内(几十秒)完成或看到系统的数据更新。</p> <p>那么zab是如何确保这些一致性相关的特色。</p> <p>Zab的两个重要的要求以下:</p> <p>一、 支持同时处理多个outstanding的客户端写操做。一个outstanding事务的含义是事务已经被提交但没有被commit。</p> <p>二、 有效的从crash状态恢复过来。</p> <p>Zookeeper能处理并发地处理多个客户端的outstanding 写请求,而且以FIFO顺序commit这些写操做。FIFO的特性对于zookeeper可以有效的从crash状态恢复过来也是相当重要的。</p> <p>原始的paxos协议不能同时处理多个outstanding transaction,paxos不要求通讯时的FIFO通道特性,paxos能够容忍消息丢失和从新排序。</p> <p>在paxos中,从primary crash中恢复过来并保证事务的序列化的能力不是足够有效,而zab改进了这方面的能力,采用了一个事务ID来实现事务的totally order。</p> <p>Zookeeper的性能要求以下:</p> <p>一、 低延时(low latency)。</p> <p>二、 Good throughput。高吞吐量。</p> <p>三、 Smooth failure handling。容错。</p> <p>在这种状况下,为了能有效地更新一个new primary的应用程序状态,在zab中new primary会被指望拥有最高事务ID的进程,整个集群能够经过从new primary中拷贝事务,从而全部数据副本均可以达到一个一致性。</p> <p>而在paxos,没有采用相似zab的序列号,因此一个新的primary须要执行paxos算法的第一阶段,以便于获取到全部primary没有学习到值。</p> <h5>2 ZAB协议和流程介绍</h5> <p>Zab协议有四个阶段,以下:</p> <p>一、阶段0:Leader election</p> <p>二、阶段1:Discovery(或者epoch establish) </p> <p>三、阶段2:Synchronization(或者sync with followers)</p> <p>四、阶段3:Broadcast</p> <p>在Zab协议的实现时,合并为三个阶段:</p> <p>一、 Fast Leader Election</p> <p>二、 Recovery Phase</p> <p>三、 Broadcast Phase</p> <p>在实现中将discovery和synchronization这两个phase合并成了broadcast phase。</p> <p>ZAB的流程图以下所示:</p> <p><a href="http://static.oschina.net/uploads/img/201312/22194147_iKYR.jpg"><img title="1" style="border-right: 0px; border-top: 0px; display: inline; border-left: 0px; border-bottom: 0px" height="322" alt="1" src="http://static.oschina.net/uploads/img/201312/22194147_K2bP.jpg" width="500" border="0" /></a> </p> <p><font size="1">CEPOCH = Follower sends its last promise to the prospective leader</font></p> <p><font size="1">NEWEPOCH = Leader proposes a new epoch e'</font></p> <p><font size="1">ACK-E = Follower acknowledges the new epoch proposal</font></p> <p><font size="1">NEWLEADER = Prospective leader proposes itself as the new leader of epoch e'</font></p> <p><font size="1">ACK-LD = Follower acknowledges the new leader proposal</font></p> <p><font size="1">COMMIT-LD = Commit new leader proposal</font></p> <p><font size="1">PROPOSE = Leader proposes a new transaction</font></p> <p><font size="1">ACK = Follower acknowledges leader proosal</font></p> <p><font size="1">COMMIT = Leader commits proposal</font></p> <h3>3 Leader election</h3> <h4>3.1 leader election后置条件</h4> <p>Leader election可能有多种方式,但在这里咱们只分析一种,fast leader election。</p> <p>Leader election后置条件:</p> <p>一、条件:Leader election这个过程必须保证选举出来的leader能看到全部历史的commited transactions。</p> <p>二、缘由:这个后置条件是为了确保在后续recovery phase步骤中zookeeper replicas的一致性。它是防止follower中包含leader中没有的committed transaction,并且在recovery phase中只有leader向follower和observer同步,follower不会向leader同步,若是出现这种状况,那么zookeeper的replicas就出现了不一致的状况。</p> <p>因此为了达到这个后置条件,leader election须要选择出一个拥有highest lastZxid的leader。</p> <p>那么fast leader election是如何选择出一个拥有highest lastZxid的leader?</p> <h4>3.2 Fast leader election介绍</h4> <p>在进行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。</p> <h4>3.3 Epoch number</h4> <p>Epoch是用于区分每个round,每一次创建一个新的leader-follower关系,都会有一个惟一的epoch值去标识。就好像皇帝登基必须得有一个年号,与以前或以后的皇帝进行区分。</p> <p>Epoch在两个过程当中用到:一、leader election时。二、recovery过程(新创建一个leader-follower关系)。</p> <p>一、过程1:每个fast leader election开始时epoch的值都为0,epoch的值会在fast leader election过程当中进行更新。</p> <p>我的理解每一个zookeeper节点刚启动时没有leader-follower关系视图,那么它就会认为本身是leader,而后发起leader electoin,那么这个leader election的epoch值为0;在leader election过程当中,将epoch更新到currepoch值(其余peer server中的最高的epoch)。使用epoch number来区分不一样的fast leader election过程。就好像你想当皇帝,定了一个年号发起登基过程,若是当前有其余皇帝存在,且他的年号比你的年号更新,那么你就得更新年号,从新发起登基,谁支持的人多谁就是皇帝;若是没有其余皇帝存在,但有其余人也在登基,那么你们就一块儿比比,看谁的年号更新,看谁的资格更老(一样的epoch,vote值越大越优先),那么选举谁当皇帝。</p> <p>二、过程2:在一个faster leader election结束后,新产生的leader会获取epoch,其值为lastest history zxid的高32位,而后对epoch自增,而后用新的epoch值做为新zxid的高32,zxid的低32位为0。一旦当上皇帝后,就发布一个新的年号。</p> <p>这里有矛盾的地方:</p> <p>两个过程的epoch是不是同一个?过程1的epoch是不会持久化的。过程2中由于zxid是持久化的,那么至关于epoch也是持久化的。因此不理解。</p> <h4>3.4 选取出highest zxid的leader</h4> <p>为了能选举出highest zxid的leader,那么就须要对vote进行比较。</p> <p>对于peer server集合 PSET = {p1, p2, p3, …., pn},其中{1, 2, 3, …. , n }是peer server的ID.</p> <p>那么Pi的vote能够用pair(Zi, i)表示,Zi是Pi的highest zxid,也是lastest zxid。</p> <p>那么两个vote比较大小的准则是:</p> <p>   (Zi, i) >= (Zj, j) : Zi > Zj 或者( Zi = Zj && i >= j )</p> <p>每个peer server都有一个惟一的ID,且都知道其replicas中保存的latest zxid,那么全部的peer就会以必定顺序进行排序。</p> <h4>3.5 Fast leader election持久化</h4> <p>在fast leader election过程当中,不会对任何数据进行持久化,不会把过程当中产生的值写入到disk中。包括epoch number和ID但在fast leader election会使用已经持久化的latest zxid。</p> <h4>3.6 Fast leader election过程和伪码</h4> <p>进行Fast leader election的先决条件:</p> <p>一、 每一个peer server都知道其余peer的ip地址,并知道peer server的总数。</p> <p>二、 每一个peer server一开始都是发起一个vote,选取本身为leader。向其余全部的peer server发送vote的notification,并等待回复。</p> <p>三、 根据peer server的状态处理vote notification或则notifincation的回复.</p> <p>若是peer server处于election状态,那么peer server会收到其余peer server的vote,若是收到的vote值更大,那么peer server会更新其vote。</p> <p>若是peer server不处于election状态,那么peer server会更新其所看到的leader-follower关系。</p> <p>无论哪一种状况下,当peer server检测到大部分peers持有相同的vote时,那么它会返回</p> <p><strong>Fast leader election逻辑伪代码</strong></p> <p>主要有两个逻辑分支:</p> <p>一、正常过程,vote的notification的回复的peer server的状态为election</p> <p>二、另外过程,vote的notification的回复的peer server的状态为leading/following</p> <p>执行leader election的状况较为复杂,多是一个服务器节点新加入到zookeeper集群中。也多是zookeeper集群刚启动,你们都处于leader election状态。以上两个逻辑分支能处理这些状况。</p> <p><font color="#800000">***初始化vote和peer server状态***</font></p> <p><font color="#008000">1 Peer P:</font></p> <p><font color="#008000">2 timeout <---T0 // use some reasonable timeout value</font></p> <p><font color="#008000">3 ReceivedVotes <--- 0; OutOfElection <--- 0; // key-value mappings where keys are server ids</font></p> <p><font color="#008000">4 P:state <--- election;  P:vote <---(P:lastZxid; P:id);  P:round <--- P:round + 1</font></p> <p><font color="#800000">1-4是初始化过程,设置超时时间,receivedVotes是收到的vote noficaton回复。</font></p> <p><font color="#800000">进入election状态,根据lastZxid和ServerID生成一个vote,vote的epoch自增。</font></p> <p><font color="#800000">ReceivedVotes做为一个结果集合,在收到全部vote后,进行表决。OutOfElection用于保存状态为leading/folling的rspvote,用于表决先存在的leader/follower是否有效。</font></p> <p><font color="#008000">5 Send notification (P:vote, P:id, P:state, P:round) to all peers</font></p> <p><font color="#800000">向全部的peer server发送notification,一个notification包括vote,id,peer state,和vote的epoch number。</font></p> <p><font color="#800000">***开始接收notification回复的循环处理***</font></p> <p><font color="#008000">6 while P:state = election do</font></p> <p><font color="#008000">7     n <---(null if P:queue = 0; for timeout milliseconds, otherwise pop from P:queue)</font></p> <p><font color="#008000">8     if n = null then</font></p> <p><font color="#008000">9          Send notification (P:vote, P:id, P:state, P:round) to all peers</font></p> <p><font color="#008000">10        timeout <---(2* timeout), unless a predefined upper bound has been reached</font></p> <p><font color="#800000">8-10是当notification回复为空时,有两种状况,一种是信令发送出去回复超时,第二种是没有创建于peer server的链接.</font></p> <p><font color="#800000">若是是第一种状况,那么从新发送notification;若是是第二种状况,那么创建与peer server的tcp链接.</font></p> <p><font color="#008000">11    else if n:state = election then </font><font color="#800000">//当nofication回复不为空,且peer server的状态也是election时</font></p> <p><font color="#008000">12         if n:round > P:round then</font></p> <p><font color="#008000">13               P:round <--- n:round</font></p> <p><font color="#008000">14               ReceivedVotes <---0</font></p> <p><font color="#008000">15               if n:vote > (P:lastZxid; P:id) then P:voteßn:vote</font></p> <p><font color="#008000">16               else P:vote <---(P:lastZxid; P:id)</font></p> <p><font color="#008000">17               Send notification (P:vote, P:id, P:state, P:round) to all peers</font></p> <p><font color="#800000">这个逻辑分支是notification回复中resvote的epoch要大于vote</font></p> <p><font color="#800000">的epoch(说明回复中的peer vote的zxid > vote的zxid),那么vote失效了,须要更新vote,比较回复中的两个vote值的大小,选择值大的vote,而后从新发送notification。</font></p> <p><font color="#008000">18         else if n:round = P:round and n:vote > P:vote then</font></p> <p><font color="#008000">19              P:vote <--- n:vote</font></p> <p><font color="#008000">20              Send notification (P:vote, P:id, P:state, P:round) to all peers</font></p> <p><font color="#800000">      当回复中的rspvote的epoch等于vote的epoch,但rspvote > vote,那么更新vote信息</font></p> <p><font color="#800000">      而后从新将vote向全部的peer server发送。</font></p> <p><font color="#008000">21          else if n:round < P:round then goto line 6</font></p> <p>     Resvote的epoch小于vote的epoch,那么这个回复是无效的,</p> <p>       直接忽略,继续下一个循环。</p> <p><font color="#008000">22          Put(ReceivedVotes(n:id); n:vote; n:round)</font></p> <p>    将rspvote放入到ReceivedVotes中。</p> <p>23         if  ReceivedVotes = SizeEnsemble then</p> <p>24                DeduceLeader(P.vote.id);  return P.vote</p> <p>     若是已经收到了全部peer server的vote,若是vote中的leaderID == currentPeer自己,</p> <p>     那么currPeer为leader,结束并返回这次vote结果。</p> <p>25         else if P.vote has a quorum in ReceivedVotes</p> <p>                       and there are no new notifications within T0 milliseconds then</p> <p>26                DeduceLeader(P.vote.id);  return P.vote</p> <p>       若是收到超过半数peer server的vote,那么vote中的leaderID == currentPeer自己,</p> <p>          那么currPeer为leader,结束并返回这次vote结果.</p> <p>27          end</p> <p>     逻辑分支1总结:</p> <p>         若是rspvote中epoch > vote epoch,更新epoch和vote后从新发起vote</p> <p>         若是rspvote中epoch < vote epoch,无效rspvote</p> <p>         其余,都保存在结果集合中,若是有rspvote>vote,那么将vote更新到rspvote;等待全部rspvote都收到,那么vote的值应该为结果集合中最大值,若是结果集合超过半数,那么这次vote生效,leader为vote中的serverID。若是serverID为自己的serverID,那么currpeer的状态为leader不然为follower</p> <p>28    else // state of n is LEADING or FOLLOWING</p> <p>当rspvote的状态为following或leading,说明vote以外已经存在了一个leader,那么此段逻辑主要是分红两部分:一部分是vote的表决;另外一部分是vote以外的leader/follower表决.</p> <p>29         if n:round = P:round then</p> <p>30             Put(ReceivedVotes(n.id); n:vote; n:round)</p> <p>31             if n:state = LEADING then</p> <p>32                 DeduceLeader(n:vote:id); return n:vote</p> <p>33             else if n:vote:id = P:id and n:vote has a quorum in ReceivedVotes then</p> <p>34                 DeduceLeader(n:vote:id); return n:vote</p> <p>35             else if n:vote has a quorum in ReceivedVotes and the voted peer n:vote:id is in</p> <p>                     state LEADING and n:vote:id 2 OutOfElection then</p> <p>36                  DeduceLeader(n:vote:id); return n:vote</p> <p>37             end</p> <p>38         end</p> <p>以上部分是vote的表决,以上的逻辑跟代码中不符合,代码中的逻辑是:</p> <p>若是rspvote的epoch==vote的epoch,放入到receivedVots中,若是rspvote的状态是leader</p> <p>且集合中的rspvote超过半数,那么vote的表决的leader就是rspvote的leader。</p> <p>39         Put(OutOfElection(n:id); n:vote; n:round)</p> <p>40         if n:vote:id = P:id and n:vote has a quorum in OutOfElection then</p> <p>41             P:round <--- n:round</p> <p>42             DeduceLeader(n:vote:id); return n:vote</p> <p>43         else if n:vote has a quorum in OutOfElection and the voted peer n:vote:id is in state</p> <p>                     LEADING and n:vote:id 2 OutOfElection then</p> <p>44             P:round <--- n:round</p> <p>45             DeduceLeader(n:vote:id); return n:vote</p> <p>46          end</p> <p>以上部分是对vote以外的leader/follower进行表决,OutOfElection是用来存放状态为leader/follow的rspstate,若是OutOfElection的rspvote超过半数,那么说明election以外的leader./follow是有效地,</p> <p>47  end</p> <p>   逻辑分支2总结:这部分是考虑到可能有部分peer server维持leader/follower的状态,部分peer server处于election状态,若是维持leader/follower状态的peer server数据过半,那么leader/follower就是有效地。或者vote的epoch等于leader的epoch,那么若是有半数以上的rspvote,那么当前的leader/follower也是有效的。</p> <p></p> <h3>4 Discovery and synchronization</h3> <p>在broadcast阶段,zookeeper集群必须有一个leader peer,zookeeper集群是primary/backup模式,那么leader就是primary。Discovery和synchronization这两个阶段的做用就是将所有的zookeeper节点带入到一个最终一致的状态,特别是当从crash中恢复时。这两个阶段组成了zab的recovery部分,对于容许多个独立事务的状况下,保证事务的顺序起着关键做用。</p> <p>无论在discovery、synchronization仍是broadcast,一旦发生错误,那么均可以回到leader election过程。</p> <p>用户若是须要使用zookeeper服务,那么必须链接一个zookeeper节点。用户向链接的服务器提交写操做,而后zab协议层会执行一个broadcast;假如用户向follower提交写操做,那么follower会把写操做发送给leader;若是leader收到写操做,leader会执行,而后向全部follower扩散这个写操做对应的数据更新。读操做能够由与用户相链接的zookeeper节点直接完成。用户能够经过发送sync命令保证数据副本的更新。</p> <p>在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。</p> <p>当一个新的epoch开始时,一个新的leader会被激活,此时c会被设置为0,e会在前一个epoch的值上+1。</p> <p>在代码实现中e是zxid的高32位,c是zxid的低32位。</p> <p>如下四个变量构成了一个peer的持久化状态:</p> <p>一、History:已经被接受的事务提案(transaction proposal)。</p> <p>二、acceptedEpoch:最近收到的NEWEPOCH信令中的epoch number。</p> <p>三、currentEpoch:最近收到的NEWLEADER信令中的epoch number。</p> <p>四、lastZxid:history中的最近的zxid。</p> <h3>5 Discovery过程</h3> <p>在这个阶段,followers会跟他们的将来预期中的leader进行通讯,准leader会收集accepted follower(已经创建链接的)的latest transactions,这个阶段的目的是发现quorum peer server中的highest histroy transaction,而后创建一个新的epoch,这样就能够防止previous leader不会commit 新的proposals(由于previous leader的epoch已通过期了)。</p> <p>在discovery阶段的开始,一个follower peer会创建于准leader的leader-follower connection。</p> <p>Follower同时只是链接一个leader。假如一个peer P不是leading状态,其余peer会考虑p是一个准leader,任何其余leader-follower链接都会被p拒绝;一样leader-follower链接的拒绝或其余的failure能将follower从新带入到leader election状态。</p> <p>1 Follower F:</p> <p>2 Send the message FOLLOWERINFO(F:acceptedEpoch) to L</p> <p>3   upon receiving NEWEPOCH(e0) from L do</p> <p>4      if e0 > F:acceptedEpoch then</p> <p>5          F:acceptedEpoch <--- e0 // stored to non-volatile memory</p> <p>6          Send ACKEPOCH(F:currentEpoch; F:history; F:lastZxid) to L</p> <p>7           goto Phase 2</p> <p>8      else if e0 < F:acceptedEpoch then</p> <p>9           F:state <--- election and goto Phase 0 (leader election)</p> <p>10     end</p> <p>11 end</p> <p>这个过程是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阶段。</p> <p>12 Leader L:</p> <p>13 upon receiving FOLLOWERINFO(e) messages from a quorum Q of connected followers do</p> <p>14      Make epoch number e0 such that e0 > e for all e received through FOLLOWERINFO(e)</p> <p>15      Propose NEWEPOCH(e0) to all followers in Q</p> <p>16 end</p> <p>17 upon receiving ACKEPOCH from all followers in Q do</p> <p>18      Find the follower f in Q such that for all f0 2 Q n ffg:</p> <p>19          either f0:currentEpoch < f:currentEpoch</p> <p>20          or (f0:currentEpoch = f:currentEpoch) ^ (f0:lastZxid _z f:lastZxid)</p> <p>21      L:history <--- f:history  // stored to non-volatile memory</p> <p>22      goto Phase 2</p> <p>23 end</p> <p>这个是leader端的recovery过程,leader会生产一个new epoch,首先接收全部follower的epoch,肯定new epoch要大于全部的follower epoch。而后向全部follower发送NEWEPOCH信令,将new epoch下发到全部的follower中。</p> <p>等待follower的ACKEPOCH回复,若是全部的follower的currEpoch和zxid都小于等于leader的currEpoch和zxid,那么进入下一个过程。</p> <h3>6 Synchronization过程</h3> <p>这个过程是将follower的数据副本与准leader的历史数据进行同步,使得zookeeper集群的数据处于一致的状态。同步的方向是准leader向follower同步。同步的过程以下:leader与follower进行通讯,发送NEWLEADER信令,带有历史事务的highest zxid;follower收到这些信令后,决定是否更新历史事务,而后响应leader。当leader看到quorum follower的响应后,就会向它们发送commit信令。在这以后leader就创建完成了。</p> <p>1 Leader L:</p> <p>2 Send the message NEWLEADER(e0;L:history) to all followers in Q</p> <p>3 upon receiving ACKNEWLEADER messages from some quorum of followers do</p> <p>4      Send a COMMIT message to all followers</p> <p>5      goto Phase 3</p> <p>6 end</p> <p>这是leader端的过程,发送NEWLEADER,而后接受响应,最后发送commit,至此leader创建完毕。</p> <p>7 Follower F:</p> <p>8 upon receiving NEWLEADER(e0;H) from L do</p> <p>9      if F:acceptedEpoch = e0 then</p> <p>10         atomically</p> <p>11             F:currentEpoch <--- e0 // stored to non-volatile memory</p> <p>12             for each (v; z) in H, in order of zxids, do</p> <p>13                  Accept the proposal (e0; (v; z))</p> <p>14             end</p> <p>15             F:history <---H // stored to non-volatile memory</p> <p>16         end</p> <p>17         Send an ACKNEWLEADER(e0;H) to L</p> <p>18     else</p> <p>19          F:state <--- election and goto Phase 0</p> <p>20     end</p> <p>21 end</p> <p>22 upon receiving COMMIT from L do</p> <p>23      for each outstanding transaction (v; z) in F:history, in order of zxids, do</p> <p>24          Deliver (v; z)</p> <p>25      end</p> <p>26      goto Phase 3</p> <p>27 end</p> <p>这是follower端的流程,先是收到NEWLEADER信令,而后原子地更新epoch和历史事务,发送ACKNEWLEADER信令响应leader;而后等待commit信令,收到commit信令后进行处理,进入下一个阶段。</p> <h3>7 代码实现的Recovery phase</h3> <p>在实现discovery和synchronization时,没有严格分红两个阶段进行实现,在实现时进行了一些优化,合并成一个阶段实现,那么这个阶段就是recovery phase;recovery阶段就是将全部的zookeeper集群的数据副本进入到最终一致性地状态中,且创建出一个具备最高highest zxid的leader。</p> <p>在实现中,第0阶段的fast leader election与第一阶段discovery紧密结合在一块儿,faster leader election在实现时作了一个优化,它会选择出一个most up-to-date的history(我的理解就是选择出一个具备最新的commit事务的peer server),那么这样的一个leader被选举出来后,在第一阶段就不须要去与followers通讯去发现latest history。</p> <p>那么既然在fast leader election中包括了discovery阶段的责任,那么这个discovery阶段就能够被忽略,因此在实现时就将discovery和synchornization阶段合并成一个recovery阶段。这个阶段是在fast leader election以后,且认为leader拥有lastest history。</p> <p>伪码:</p> <p>1 Leader L:</p> <p>2 L:lastZxid <--- (L:lastZxid:epoch + 1; 0)</p> <p>3 upon receiving FOLLOWERINFO(f:lastZxid) message from a follower f do</p> <p>4      Send NEWLEADER(L:lastZxid) to f</p> <p>5      if f:lastZxid  <=  L:history:lastCommittedZxid then</p> <p>6          if f:lastZxid  <=  L:history:oldThreshold then</p> <p>7              Send a SNAP message with a snapshot of the whole database of L</p> <p>8          else</p> <p>9              Send a DIFF({committed transaction (v; z) in L:history : f:lastZxid < z})</p> <p>10        end</p> <p>11     else</p> <p>12         Send a TRUNC(L:history:lastCommittedZxid) message to f</p> <p>13     end</p> <p>14 end</p> <p>15 upon receiving ACKNEWLEADER messages from some quorum of followers do</p> <p>16     goto Phase 3 // Algorithm 3</p> <p>17 end</p> <p>以上是leader端的流程,先生存一个新的zxid和epoch,接收follower的FOLLOWERINFO信令(包含follower的lastzxid),而后向follower发送NEWLEADER(包含leader的zxid)。而后根据FOLLOWERINFO中带有的lastzxid对follower进行更新。分红三种状况…….</p> <p>History.lastCommittedZxid是最新committed的历史事务。History.oldThreshold是过久的历史提案,比leader上一次snapshot的时间还久。见2.6.2关于TRUNC的说明。</p> <p>第一种状况是TRUNC,follower丢弃从leader.latestZxid到follower.lasterZxid之间的提案。</p> <p>第二种状况是DIFF,follower接收新的提案从follower.lasterZxid到leader.lasterZxid之间的新提案。</p> <p>第三种状况是SNAP,follower中的提案太旧,leader将snap更新到follower上。</p> <p>18 Follower F:</p> <p>19 Connect to its prospective leader L</p> <p>20 Send the message FOLLOWERINFO(F:lastZxid) to L</p> <p>21 upon L denies connection do</p> <p>22     F:state <--- election and goto Phase 0</p> <p>23 end</p> <p>24 upon receiving NEWLEADER(newLeaderZxid) from L do</p> <p>25     if newLeaderZxid:epoch < F:lastZxid:epoch then</p> <p>26         F:state <--- election and goto Phase 0</p> <p>27     end</p> <p>28     upon receiving a SNAP, DIFF, or TRUNC message do</p> <p>29         if got TRUNC(lastCommittedZxid) then</p> <p>30             Abort all proposals from lastCommittedZxid to F:lastZxid</p> <p>31         else if got DIFF(H) then</p> <p>32             Accept all proposals in H, in order of zxids, then commit all</p> <p>33         else if got SNAP then</p> <p>34             Copy the snapshot received to the database, and commit the changes</p> <p>35         end</p> <p>36         Send ACKNEWLEADER</p> <p>37         goto Phase 3 // Algorithm 3</p> <p>38    end</p> <p>39 end</p> <p>以上是follower的流程,首先是向leader链接,而后发送FOLLOWERINFO信令,若是leader拒绝链接,那么follower从新回到leader election阶段。接收NEWLEADER信令,若是信令中带有的epoch无效(小于follower的epoch),那么follower从新回到leader election状态。</p> <p>而后接收SNAP/DIFF/TRUNC信令,同步数据副本和zxid,最后回复ACKNEWLEADER信令。进入到下一个阶段。</p> <p>这个同步的目的是让全部数据副本都进入一个最终一致性状态。为了达到这个目的,任何副本中的committed transactions必须以一样一种顺序,甚至已经被提交的transaction但没有被任何一个peer节点committ的事务必须被抛弃。SNAP和DIFF用于保证各个副本中的committed事务的顺序一致性;而TRUNC用于处理已经被提交但没有被committed的事务。</p> <h3>8 Broadcast phase</h3> <p>Zookeeper peer之间的双向通道使用TCP链接实现,TCP通讯的FIFO序列化特性对于实现broadcast协议相当重要。</p> <p>假如没有发生崩溃,那么peers会一直停留在broadcast阶段。第三阶段中只能有一个leader。</p> <p>Broadcast的过程是leader与follower之间的一个两阶段的提交过程(two-phase commit)</p> <p>一、 leader与follower的通信通道(communication channel)是一个FIFO,全部都是是按顺序处理。</p> <p>二、 leader收到一个request后,会生成一个propose。而后执行两阶段提交.</p> <p><a href="http://static.oschina.net/uploads/img/201312/22185922_040N.png"><img title="wps_clip_image-14769" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="126" alt="wps_clip_image-14769" src="http://static.oschina.net/uploads/img/201312/22185922_KHTe.png" width="244" border="0" /></a></p> <p>Broadcast的伪码和流程</p> <p>1 Leader L:</p> <p>2 upon receiving a write request v do</p> <p>3     Propose (e0; (v; z)) to all followers in Q, where z = (e0; c), such that z succeeds all zxid</p> <p>       values previously broadcast in e0 (c is the previous zxid's counter plus an increment of one)</p> <p>4 end</p> <p>5 upon receiving ACK((e0; (v; z))) from a quorum of followers do</p> <p>6     Send COMMIT(e0; (v; z)) to all followers</p> <p>7 end</p> <p>以上是leader处理的两阶段提交的流程:首先leader受到写请求v,而后生成一个提案(e,(v,z)),向全部follower发送此提案的内容,而后等待follower的ack;若是ack超过半数,那么提案成立。向全部follower下发commit提案的命令。</p> <p>8 // Reaction to an incoming new follower:</p> <p>9 upon receiving FOLLOWERINFO(e) from some follower f do</p> <p>10     Send NEWEPOCH(e0) to f</p> <p>11     Send NEWLEADER(e0;L:history) to f</p> <p>12 end</p> <p>13 upon receiving ACKNEWLEADER from follower f do</p> <p>14     Send a COMMIT message to f</p> <p>15     Q <--- Q 并集 {f}</p> <p>16 end</p> <p>以上是一个新follower加入leader的流程:首先leader收到FOLLOWERINFO信令,而后向new follower发送NEWEPOCH信令,再发送NEWLEADER信令给new follower;等待new follower的ACKNEWLEADER,最后发送commit,至此new follower就加入到了集群中。</p> <p>17 Follower F:</p> <p>18 if F is leading then Invokes ready(e0)</p> <p>19 upon receiving proposal (e0; (v; z)) from L do</p> <p>20     Append proposal (e0; (v; z)) to F:history</p> <p>21     Send ACK((e0; (v; z))) to L</p> <p>22 end</p> <p>23 upon receiving COMMIT(e0; (v; z)) from L do</p> <p>24     while there is some outstanding transaction (v0; z0) in F:history such that z0 < z do</p> <p>25         Do nothing (wait)</p> <p>26     end</p> <p>27     Commit (deliver) transaction (v; z)</p> <p>28 end</p> <p>这是follower的broadcast流程:接收到leader的提案,而后将提案写入到history中,而后发送响应。等待leader的commit信令,收到后执行commit 提案。</p> <h3>9 Zab所存在的问题</h3> <h4>9.1 acceptedEpoch和currentEpoch的做用</h4> <p>在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阶段之间循环。</p> <p>因此使用lastZxid来存储epoch number,没有对一个tried epoch(我的理解是一个准leader在尝试成为leader时使用的epoch)和一个joined epoch(一个成功的leader所使用的epoch)进行区分。使用acceptedEpoch和currentEpoch的目的就是在于防止此类问题的发生。</p> <h4>9.2 Abandon follower proposal</h4> <p>假设一个集合{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))。</p> <p>在这种状况下,</p> <p>p1.lastestZxid(1,4) < p2.history.lastCommittedZxid(2, 1) </p> <p>&& p2.history.oldThreshold(1, 1)< p1.lastestZxid (1, 4),那么这种状况下leader p2须要向p1发送TRUNC信令,让follower放弃uncommitted proposal(1, 4)。</p> <p></p> <p></p> <p></p> <p>做者zy,QQ105789990</p>node