Leader选举算法
一句话概况选举算法精髓
所有节点都有两个属性,SID:节点ID,zoo.cfg中配置的myid,ZXID:节点当前的最大事务ID
选举的目的就是选目前所有节点中拥有最大ZXID的节点作为Leader,如果拥有的ZXID相同,就选取SID最大的节点作为Leader
选举算法流程解析–lookForLeader
在实现选举的过程中,其实还有很多细节需要注意,那么接下来看下选举的具体细节:
在ZooKeeper中,提供了3种Leader的选举算法,分别是LeaderElection、 UDP版本的FastLeaderElection、TCP版本的FastLeaderElection,可以通过再配置文件zoo.cfg中使用electionAlg属性来指定。从3.4.0版本开始,ZooKeeper废弃了前2种算法,只保留了TCP版本的FastLeaderElection算法。所有算法都实现了Election接口:
public interface Election {
//选举的核心算法
public Vote lookForLeader() throws InterruptedException;
//关闭选举过程中创建了链接及启动的线程
public void shutdown();
}
先看下选举算法核心部分(FastLeaderElection.lookForLeader)的流程示意图:
lookForLeader
详细源码
- 每次选举开始,各个节点都会先为自己投票(原子操作),其中logicalclock代表选举逻辑时钟(类比现实中的第十八次全国人大、第十九次全国人大……),这个值从0开始递增,在同一次选举中,各节点的值基本相同,也有例外情况,比如在第18次选举中,某个节点A挂了,其他节点完成了Leader选举,但没过多久,该Leader又挂了,于是进入了第19次Leader选举,同时节点A此时恢复,加入到Leader选举中,那么节点A的logicallock为18,而其他节点的logicallock为19,针对这种情况,节点A的logicallock会被直接更新为19并参与到第19次Leader选举中。
其中getInitId()是当前节点的serverId, getInitLastLoggedZxid()是当前节点的最大事务ID,getPeerEpoch当前节点选举轮次,即当前节点Zxid的高位代表的epoch其中getInitId()是当前节点的serverId, getInitLastLoggedZxid()是当前节点的最大事务ID,getPeerEpoch当前节点选举轮次,即当前节点Zxid的高位代表的epoch
public Vote lookForLeader() throws InterruptedException {
{...}
try {
HashMap<Long, Vote> recvset = new HashMap<Long, Vote>();
HashMap<Long, Vote> outofelection = new HashMap<Long, Vote>();
int notTimeout = finalizeWait;
//1.初始化选票,首先为自己投票
synchronized(this){
logicalclock++;
updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());
}
//2.向所有节点发送投票信息
sendNotifications();
/*
* Loop in which we exchange notifications until we find a leader
* 3.循环交换投票直至选出Leader
*/
while ((self.getPeerState() == ServerState.LOOKING) &&
(!stop)){...}
}
}
- 向所有节点发送投票信息
private void sendNotifications() {
for (QuorumServer server : self.getVotingView().values()) {
long sid = server.id;
ToSend notmsg = new ToSend(ToSend.mType.notification,
proposedLeader, //推荐的leader的id,就是配置文件中写好的每个服务器的id
proposedZxid, //推荐的leader的zxid,zookeeper中的每份数据,都有一个对应的zxid值,越新的数据,zxid值就越大
logicalclock, //选举的逻辑时钟
QuorumPeer.ServerState.LOOKING,
sid,
proposedEpoch);
sendqueue.offer(notmsg);
}
}
- 循环交换投票直至选出Leader
while ((self.getPeerState() == ServerState.LOOKING) &&
(!stop)){
//从接收投票的队列中取出一条投票信息
Notification n = recvqueue.poll(notTimeout,
TimeUnit.MILLISECONDS);
if(n == null){...}
else if(self.getVotingView().containsKey(n.sid)) {
switch (n.state) {
case LOOKING://拿别人的票跟自己对比,谁的更合理,就发出去更合理的选票(类似于冒泡排序算法,每次对比,都选比自己大/小的数)
case OBSERVING://观察者是不参与Leader选举的
case FOLLOWING:
case LEADING://当已经收到LEADING和FOLLOWI