Leader选举的实现细节
服务器状态
- LOOKING:寻找Leader状态,当服务器处于该状态的时候,它会认为当前集群中没有Leader,因此需要进入Leader选举流程
- FOLLOWING:跟随者状态,表明当前服务器角色是Follower
- LEADING:领导者状态,表明当前服务器角色是Leader
- OBSERVING:观察者状态,表明当前服务器角色是Observer
投票数据结构
- id:被推举的Leader的SID值,即myid中设置的参数
- zxid:当前投票的事务ID
- electionEpoch:逻辑时钟,每次进入新一轮的投票后,都会对该值进行加1操作,用来判断多个投票是否在同一轮选举周期中
- peerEpoch:被推举的Leader的epoch
- state:当前服务器的状态
消息队列
每台服务器启动的时候,都会启动一个QuorumCnxManager,负责各台服务器之间底层的Leader选举过程中的网络通信。
- recvQueue:消息接收队列,用于存放那些从其他服务器接收到的消息
- queueSendMap:消息发送队列,key(集群中其他机器的SID),value(消息内容)
- senderWorkerMap:消息发送器集合,key对应SID,value对应每一台远程的ZooKeeper服务器
- lastMessageSent:最近发送过的消息。在这个集合中,为每个SID保留最近发送过的一个消息
算法核心
1. 自增选举轮次
在FastLeaderElection实现中,有一个logicalclock属性,用于标识当前Leader的选举轮次,ZooKeeper规定了所有有效的投票都必须在同一轮次中。ZooKeeper在开始新一轮的投票时,会首先对logicallock进行自增操作
2. 初始化选票
初始化Vote数据结构
- id:当前服务器自身的SID
- zxid:当前服务器最新的ZXID值
- electionEpoch:当前服务器的选举轮次
- peerEpoch:被推举的服务器的选举轮次
- state:LOOKING
3. 发送初始化选票
服务器发起第一次投票。ZooKeeper会将刚刚初始化的Vote放入sendqueue队列中,由发送器WorkerSender负责发送出去。
4. 接收外部选票
每台服务器会不断地从recvqueue队列中获取外部投票。如果服务器发现无法获取到任何的外部投票,那么就会立刻确认自己是否和集群中其他服务器保持有效连接;如果发现没有建立连接,就会建立连接,并再次发送自己当前的内部投票。
5. 判断选举轮次
根据选举的轮次处理外部投票
外部投票的选举轮次大于内部投票
更新自己的选举轮次(logicalclock),并且清空所有已经收到的选票,使用新初始化的投票与外部投票进行PK,最终再将内部投票发送出去外部投票的选举轮次小于内部投票
ZooKeeper忽略该外部投票,返回步骤4外部投票的选举轮次与内部投票一致
进行选票PK
注意,只有在同一选举轮次的投票才是有效的投票
6. 选票PK
- 外部投票中被推举的Leader服务器的选举轮次大于内部投票,那么进行投票变更
- 如果选举轮次一致,那么对比两者的ZXID,若外部投票的ZXID较大,进行投票变更
- 如果两者的ZXID一致,那么就对比两者的SID,如果外部投票的SID较大,进行投票变更
7. 变更投票
通过选票PK之后,如果确定了外部投票优于内部投票(所谓的“优于”,是指外部投票所推举的服务器更适合成为Leader),那么进行投票变更——使用外部投票的选票信息来覆盖内部投票。变更完成之后,再次将这个变更的内部投票发送出去。
8. 选票归档
无论是否进行了投票变更,都会将刚刚收到的那份外部投票放入“选票集合”recvSet中进行归档,recvSet用于记录当前服务器在本轮次中的Leader选举中收到的所有外部投票——按照服务器对应的SID来区分,例如{(1, vote1), (2, vote2), …}
9. 统计投票
完成了选票归档之后,进行统计投票。如果确认已经有过半的服务器认可了该内部投票,则终止投票。否则返回步骤4。
10. 更新服务器状态
统计投票后,如果已经确定可以终止投票,那么就开始更新服务器状态。服务器首先判断当前被过半服务器认可的投票所对应的Leader服务器是否是自己,不过是自己的话,那么就会将自己的服务器状态更新为LEADING;否则,根据情况来确定自己是FOLLOWING或者是OBSERVING。