Zookeeper Leader Election 选举算法

1,261 阅读3分钟

1. 选举流程

ZXID leader的选举流程依赖于ZXID,ZXID的构成是高位是epoch信息,也就是年代信息,每一个成为节点的leader都是一个皇帝,有着它自己的年代,这样当重新选举的时候,就可以知道其他follower节点都是上一代皇帝的数据,不准确,需要更新新epocd的数据。低位是事务ID,和mysql类似,每一个事务的事务id会递增。

一个集群中的ZK节点向所有集群中的节点发起投票信息开始选举vote(voteid,zxid),这里的voteid一开始默认是自己的id,zxid是当前节点最新的epoch+事务id。其他节点收到这个投票信息的时候,会去判断,收到的zxid是不是比自己的zxid新,如果新那么将更新自己的zxid和voteid,如果和自己相等(比如刚启动所有都没有接受事务),那么mySid(节点唯一id)和voiteid比较,取较大的值更新自己的投票信息,更新结束后,继续吧更新后的发送到其他节点,以此往复,所有的节点将会收到最新的vote信息,再等一定的finalize时间没有接受到信息选票信息,那么将结束选票流程,决定出leader节点。

如图,三个节点同时发起vote投票。

  • 时刻1,node2接收到了node3的投票,发现node3的投票比自己的新,那么就更新自己的投票信息为(3,6),然后发送其他两个节点。
  • 时刻2,姗姗来迟的node1的投票信息到达node2,这是后node2发现投票信息比自己的新,那么更新自己的投票信息为(1,8),在此发送到其他两个节点。
  • 时刻三,node3第一次收到node1节点的时候发现比自己的数据新,但是由于网络或者其他的原因,知道时刻三才将自己的(1,8)的投票信息发送出去。
  • 由于有finalzewaite的限制,各个节点更新完投票信息之后不会立刻终止选举,都会等待一定时间再终止,最后的结果就是三个node节点达成统一,投票node1为leader节点。

2. 源码分析

在集群模式下一个节点的入口方法是QuorumPeerMain类中的main方法。

runFromConfig中创建一个QuorumPeer对象,quorumPeer继承了Thread类,实现了run方法,runFromConfig中配置quorumPeer对象后,调用start另起线程启动它,并且join等待。

我们下面再来看一下他的start方法,标注的红框初始化了事务日志快照监听端口等,然后创建并启动了LeaderElection算法对象,最后调用了Thread类的start(),这个start会创建一个新的线程调用QuorumPeer类的run()方法,这个时候才真正启动了另一个线程,而上异步runfromConfig中join()阻塞等待的也是这个线程跑完run方法。

进入startLeaderElection()方法,这里创建和启动了选举对象,看一下里面调用了createElectionAlgorithm()方法,这里真正创建了选举算法。

LederElection类中有两个队列

  • sendqueue:投票发送队列,里面存放ToSend对象,manager中的wsThread会在此队列中消费,进行投票信息发送。
  • recvqueue:投票接受队列,里面存放Notifacation对象,manager中的wrThread会在此队列中进行消费,进行投票信息接受。

下面再回到QuorumPeer start之后的run()方法中,在上面配置好选举算法后,会异步进入到run()中

进入lookForLeader看一下

再进入sendnotifications()看一下

再回到lookForLeader方法,上半部分进行是第一次启动运行,上半部分运行结束后,那么进入loop循环进行选票接收,直到选出一个leader否则会一直loop

在上面的接收结束后,会判断是否已经有节点收到了超过半数的选票,若已收到,那么会等待finalzewaite时间后,没有新的选票进入,那么将会终止选举过程,选举成功。