Zookeeper ZAB原子广播协议和Leader选举

文章目录

  • 消息广播
  • 崩溃恢复
  • ZXID设计原理
  • 协议状态
  • ZAB算法
    • 选举
    • 发现
    • 同步
    • 广播
      • 数据一致性
      • 数据顺序性
  • 参考

zk使用ZAB(zookeeper atomic broadcase 原子消息广播协议)作为其数据一致性的核心算法。

  1. 基于此协议,zookeeper实现了一种主备模式的系统架构来保持集群中各副本之间数据的一致性。具体的,zk使用一个单一的主进程来接收并处理客户端的所有事务请求,并采用ZAB的原子广播协议,将服务器数据的状态变更以事务proposal的形式广播到所有的副本进程上。针对客户端的并发请求,能保持较好的一致性。
  2. ZAB保证一个全局的变更序列被顺序应用,确保请求处理的顺序性
  3. ZAB协议保证在当前主进程出现崩溃退出或重启等异常情况时,依旧能够正常工作

ZAB协议包括两种基本的模式,分别是崩溃恢复和消息广播。当Leader出现网络中断、崩溃退出与重启等异常情况,ZAB协议就会进入恢复模式并选举产生新的leader服务器,并且集群中过半服务器和新的leader服务器完成状态同步后,就会退出恢复模式。而后进入消息广播模式。

消息广播

针对客户端的事务请求,Leader服务器会为其生成对应的事务Proposal,并将其发送给集群中其余所有的follow机器,follow机器收到事务proposal后,会以事务日志的形式写入到本地磁盘,在写入成功后反馈ack给leader服务器,leader服务器收集follow机器的ack,当过半的机器反馈ack后,即进行事务提交,leader会广播一个Commit消息给所有的follower机器通知进行事务提交,同时leader自身也会完成事务的提交

在广播事务proposal前,Leader服务器会为这个事务分配一个全局唯一单调递增的事务id(zxid),每一个事务按照其zxid的先后顺序来进行排序和处理,以此保证消息严格的因果关系。

崩溃恢复

当leader机器崩溃后,会触发集群的leader选举。ZAB协议确保集群可以快速选举新的leader,一方面让leader自己知道自身已被选为leader,同时让集群的其他机器也能快速感知到选举产生的新leader服务器。

ZAB协议需要处理一下leader崩溃恢复出现的数据不一致情况:

  1. ZAB协议需要确保那些已经在leader服务器上提交的事务最终被所有服务器提交。假设leader在收到过半服务器ack,将commit消息发送给所有follower机器之前挂了。
  2. ZAB协议需要确保丢弃那些只在leader服务器上被提出的事务,在leader服务器提出新事务未被其他服务器接收,leader服务器崩溃恢复成follow服务器时,要抛弃未提交的事务

基于上面两种情况处理,ZAB协议涉及的选举算法需要确保提交已经被leader提交的事务proposal,同时丢弃那些已经被跳过的事务。根据事务id的递增姓,即新选举的leader服务器拥有集群中所有机器的最高编号(zxid最大)的事务proposal。以此确保leader具有所有已经被提交的提案。同时便于新leader确认原leader最新提出的事务是无效的以便原leader抛弃未提交的事务。

ZXID设计原理

ZXID是一个64位数字,低32位是一个简单的单调递增计数器,每产生一次新的事务proposal,都会对该计数器加1,高32位可以看作leader周期epoch编号,每选举一个新的leader服务器,会从leader服务器上取出其本地日志汇总最大事务proposal的ZXID,从里面解析出epoch值,然后进行加入操作,并以此作为新的epoch,同时低32位置0来开始生成新的ZXID。epoch能有效避免不同的leader服务器错误地使用相同的ZXID编号提出不一样事务的异常情况。

基于这个策略,当一个包含上一个leader周期未提交事务proposal的服务器启动时,会尝试和集群的leader进行状态同步,会将本地最后提交的prososal的zxid和当前leader进行对比,发现低32位的ZXID在leader中不存在,且epoch比leader小,会被抛弃。

协议状态

在ZAB协议中,每一个进程都可能处于以下三种状态之一:

  1. Looking :系统刚启动时或者Leader崩溃后正处于选举状态,处于这个状态的进程,都会试图去选举出一个新的Leader。在选举出新的leader后,会切换到Following状态。
  2. Following :Follower节点所处的状态,Follower与Leader处于数据同步阶段,当leader崩溃或放弃领导地位后,所有的follower会转换到looking状态,并开始新一轮的leader选举
  3. Leading :Leader所处状态,当前集群中有一个Leader为主进程。leader和尝试和所有follower维持心跳,当在指定的超时时间内Leader无法从过半的Follower进程获取到心跳检测,或者tcp连接本身断开,leader会终止对当前周期的领导,并转换到LOOKING状态,所有的follower也会放弃这个Leader,转换到LOOKING状态。
  4. observing:观察者状态,处于此状态的观察者的行为在大多数情况下与follower完全一致, 但是他们不参加选举和投票,而仅仅接受(observing)选举和投票的结果.observer可以在不影响写性能的情况下提升集群的读操作的性能,他只接受读请求,将写请求转发给leader。

状态转换图如下所示:

ZAB算法

整个ZAB协议包括消息广播和崩溃恢复两个过程,进一步可以细分为四个阶段:选举、发现、同步、广播。

  1. 选举:在所有节点中选出具有最大zxid的机器作为准leader,其余作为follower节点
  2. 发现:follower节点向准leader推送自身最大的zxid,接收leader的NEWLEADER指令
  3. 同步:将Follower与Leader的数据进行同步,由Leader发起同步指令,最总保持集群数据的一致性;
  4. 广播:Leader广播Proposal,接收过半Follower的ack,然后广播commit,Follower接受Proposal后发送ack,再接收leader的commit完成事务同步;

选举

选举阶段需要保证选出的准leader具有最大的zxid,在选举的过程中会对每个Follower节点的ZXID进行对比只有highestZXID的Follower才可能当选Leader;

成为 leader 的条件,同时也是选票比较的判定条件

  1. 选zxid最大的
    1. 高32位epoch比较,选较大的
    2. 高32位epoch相等,比较低32位事务计数,选较大的
  2. zxid都相等,选择server id最大的(就是我们配置zoo.cfg中的myid)

这里以FastLeaderElection为例,看看zk的具体选举流程:

  1. 自增选举轮次,zk使用logicalclock来标志当前leader的选举轮次,在zk开始新一轮的投票时,回先对logicalclock进行自增操作。
  2. 初始化选票,初始化选票就是对Vote属性的初始化,同时推举自己成为leader,vote的属性包含:
    1. id:当前服务器自身的sid
    2. zxid:当前服务器最新的zxid
    3. electionEpoch:当前服务器的选举轮次
    4. state:LOOKING
  3. 发送初始化选票给集群其他服务器
  4. 每台服务器从一个接收队列中顺序获取外部投票
  5. 判断选举轮次,下面根据不同轮次,对外部机器的选票有不同处理
    1. 外部投票的选举轮次大于内部投票,这说明服务器自身的选举轮次已经落后与外部投票服务器对应的选举轮次,然后会立即更新自己的选举轮次(logicalclock),并且清空所有已经收到的投票,然后使用初始化的投票来对比是否变更内部投票,最终将内部投票发送出去
    2. 外部投票的选举轮次小于内部投票,zk服务器直接忽略该外部投票,不做处理
    3. 外部投票的选举轮次等于内部投票,这是绝大多数情况,直接进入选票比较
  6. 选票比较,根据zxid优先,其次sid的对比,如果外部投票较大,则用外部投票覆盖内部投票,变更后再次把内部投票发送出去
  7. 投票归档,无论是否投票变更,都将投票归档到recvset中,recvset记录当前服务器在本轮次的leader选举中收到的所有外部投票
  8. 统计投票,如果有过半服务器认可了内部投票,则终止投票,否则回到步骤4.
  9. 更新服务器状态,确定过半投票为leader,如果是自己,则更新自身为LEADING,否则更新自身为FOLLOWING或OBSERVING。

在选举投票过程,每台节点向其他节点发送Vote请求,其中Vote请求可记为(zxid,myid)。

  1. 每个Follower都向其他节点发送选自身为Leader的Vote投票请求,等待回复;
  2. Follower接受到的Vote(zxid1,myid2)如果比自身的大时则投票同意,并更新自身Vote(zxid1,myid2)为接受到的Vote(zxid1,myid2),否则拒绝投票;
  3. 每个Follower中维护着一个投票记录表,当某个节点收到过半的投票时,即所有Vote中的同一leader标志超过半数,结束投票并把该Follower选为Leader。
  4. 如果某些follow在当前投票轮次未决出leader,会继续发起投票。
  5. 确定了leader后,每个服务器就会更新自己的状态,如果是 Follower,那么就变更为FOLLOWING,如果是 Leader,就变更为 LEADING。

举个例子,有3个节点A(9,1),B(6,2),C(9,2)。

投票开始:

  1. A更新自身Vote为(9,3),A维护的投票记录表[(9,3),(9,3)],对外发送(9,3)
  2. B更新自身Vote为(9,3),B维护投票记录表[(9,3),(9,3)],对外发送(9,3)
  3. C维持自身Vote为(9,3),C维护投票记录表[(9,3)],对外发送(9,3)

A、B判定C为leader。C无法判定Leader,继续下一个logicalclock。继续接收外部投票,收到A、B投票,确认自己为leader。结束流程

发现

在这一阶段,主要完成准leader和follower的彼此身份确认的epoch有效性确认,依据不同视角有:

  1. leader:Leader生成新的ZXID与epoch,接收Follower发送过来的FOllOWERINFO(含有当前节点的LastZXID)然后往Follower发送NEWLEADER
  2. follower:往Leader发送FOLLOERINFO指令,Leader拒绝就转到Election阶段;接收Leader的NEWLEADER指令,如果该指令中epoch比当前Follower的epoch小那么Follower转到Election阶段。

同步

在完成leader和follower的彼此身份确认的epoch有效性确认后,follower根据Leader的最新事务日志对自身数据进行同步更新。follower会向leader发送自身的lastZXID,Leader根据Follower发送过来的LastZXID根据数据更新策略向Follower发送更新指令;

这里有三种同步策略:

  1. SNAP :如果Follower数据太老,Leader将发送快照SNAP指令给Follower同步数据;
  2. DIFF :Leader发送从Follolwer.lastZXID到Leader.lastZXID议案的DIFF指令给Follower同步数据;
  3. TRUNC :当Follower.lastZXID比Leader.lastZXID大时,Leader发送从Leader.lastZXID到Follower.lastZXID的TRUNC指令让Follower丢弃该段数据;

Follower接收SNAP/DIFF/TRUNC指令同步数据与ZXID,同步成功后回复ACKNETLEADER,然后进入下一阶段;Follower将所有事务都同步完成后Leader会把该节点添加到可用Follower列表中;

SNAP与DIFF用于保证集群中Follower节点已经Committed的数据的一致性,TRUNC用于抛弃已经被处理但是没有Committed的数据;

广播

数据一致性

客户端提交事务请求时Leader节点为每一个请求生成一个事务Proposal,将其发送给集群中所有的Follower节点,收到过半Follower的反馈后开始对事务进行提交。这导致了Leader在崩溃后可能会出现数据不一致的情况,ZAB使用了崩溃恢复来处理数据不一致问题;

数据顺序性

消息广播使用了TCP协议进行通讯所有保证了接受和发送事务的顺序性。广播消息时Leader节点为每个事务Proposal分配一个全局递增的ZXID(事务ID),每个事务Proposal都按照ZXID顺序来处理;Leader节点为每一个Follower节点分配一个队列按事务ZXID顺序放入到队列中,且根据队列的规则FIFO来进行事务的发送。Follower节点收到事务Proposal后会将该事务以事务日志方式写入到本地磁盘中,成功后反馈Ack消息给Leader节点,Leader在接收到过半Follower节点的Ack反馈后就会进行事务的提交,以此同时向所有的Follower节点广播Commit消息,Follower节点收到Commit后开始对事务进行提交;

参考

  1. https://blog.csdn.net/wangyangzhizhou/article/details/52698555
  2. 《从Paxos到Zookeeper》

你可能感兴趣的:(zookeeper,分布式)