Leader选举算法源码总结

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_41594698/article/details/102581028

emmm建议大家边看源码边看此文章啊哈,有点高度总结,没有上源码

1到7为一些属性的说明,8的lookForLeader就是核心算法

源码内部的变量有些没有按照驼峰命名,好吧我看着有点难受,不过也无所谓了

FastLeaderElection.java

  • 1

finalizeWait = 200,决定一个选举过程需要等待的选举时间,一经到达,它将结束leader选举;实际是等待其他server发送通知的超时时间;可以延长,依次*2,但不会超过maxNotificationInterval,提高选举效率

  • 2

maxNotificationInterval = 60000;finalizeWait的最大值,超过的话会开始新一轮选举

  • 3

四种状态:ServerState枚举

  • 4

LearnerType枚举有PARTICIPANT、OBSERVER

  • 5

static public class Notification:通知,包含发送者的信息(状态等)和其推荐的人的信息

  • 6

组合了QuorumPeer:管理“法定人数投票”协议,状态为leader election(对应looking)、leader、follower

  • 7

组合了QuorumCnxManager:TCP实现的用于Leader选举的连接管理器;使 用基于双方IP地址的中断连接机制来确保每对服务器正确地操作并且可以与整个网络进行通信的连接有且只有一个;内部使用一个map,key为其他server,value为发送消息失败的队列(图)

8

lookForLeader():开启新一轮的Leader选举,只要QuorumPeer的状态变为了LOOKING,此方法将被调用,发送notifications给所有其他的同级服务器;

扫描二维码关注公众号,回复: 7591977 查看本文章

每开启一轮,即调用此方法,都会调用logicalclock.incrementAndGet()递增“年号”

8.1

创建选举对象,进行初始化:当前时间(Time.currentElapsedTime)、其他server的本轮投票信息recvset(hashmap)、退出选举的票outofelection(hashmap)、收到通知的超时时间notTimeout

8.2

将自己作为新的Leader投出去:修改当前server的推荐信息,为自己;调用sendNotifications,此方法并没有真正发送,而是将推荐信息构成的数据结构notmsg添加到一个队列sendqueue(LinkedBlockingQueue);只会发送给getVotingView()的server,即不发给observer

8.3

验证当前自己的选票与大家的选票谁更适合做Leader:依次从recvqueue中取出通知进行判断,其本质是LinkedBlockingQueue

选出来后更新proposedLeader、proposedZxid、proposedEpoch,然后调用sendNotifications();leader挂了之后,有可能部分server知道了部分不知道,导致知道的部分server的logicalClock递增了,然后发给部分不知道的,此时部分不知道的依然没有收到leader的挂了的消息,当前逻辑时钟小于部分知道的server传过来的epoch,因此就有了n.electionEpoch > logicalclock.get()这种情况(epoch在本地就叫做logicalClock,传出去了就叫epoch)

haveDelivered()中可以看到,只要有一个队列为空,说明当前Server与集群没有失联(queueSendMap,key为serveID,value为向对应的server发送失败的消息副本的队列);
如果有一个server失联了,即队列全不为空,那么会调用manager.connectAll()连接所有其他server,但是并不需要重发,因为当前server若与集群失联,则其他server一定不可能收到此server发送的通知,所以其他server取不到此server的通知,即会判断n == null为true,执行if代码,重新发送通知,当前server就会收到其他server所发送的通知;
当然此前提是leader选举没结束,如果收到的票中能选出来leader,那么也就结束了,其他server就不会重发了,当前server不用重连,直接认主即可:(设计技巧)

if(n == null){//n为server收到的通知
    if(manager.haveDelivered()){
        sendNotifications();
    } else {
        manager.connectAll();
    }

最后会将来自于外部通知的选票投放到recvset

Leader的选择逻辑:totalOrderPredicate()

((newEpoch > curEpoch) || 
((newEpoch == curEpoch) && 
((newZxid > curZxid) || ((newZxid == curZxid) && (newId > curId)))));

8.4

判断本轮选举中是否应该结束了

首先当前server的推荐提案在recvset中的支持数量需要过半:如下方法会判断new的Vote在recvset中是否过半

if (termPredicate(recvset,new Vote(proposedLeader, proposedZxid,logicalclock.get(), proposedEpoch)))

然后会检查leader是否会有更适合的:
while有两个出口
1为循环条件,从这里出去说明在剩余的通知中没有找到任何比”当前过半的选票“更适合的通知,此时n为null;
2为break,从这里出去说明已经在剩余的通知中找到了一个比“当前过半的选票”更适合的通知,此时n不为null

while((n = recvqueue.poll(finalizeWait,TimeUnit.MILLISECONDS)) != null){
    if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,proposedLeader, proposedZxid, proposedEpoch)){
        //如果有更合适的,会将其再压回去
        recvqueue.put(n);
        break;
    }
}

检查完后,如果n为null,说明本轮选举可以结束了,分别修改状态为LEADING/FOLLOWING,清空接收Notification队列recvqueue,返回最终的结果选票:

if (n == null) {
	//避免多个server选中一个server的结果的方法就在这里:每个server都判断自己是否为leader即可,不要去判断别人的
    self.setPeerState((proposedLeader == self.getId()) ? ServerState.LEADING: learningState());
    Vote endVote = new Vote(proposedLeader,proposedZxid, logicalclock.get(), proposedEpoch);
    leaveInstance(endVote);
    return endVote;
}

如果n不为null,就重新开始第一步,继续拿到后面的通知(票)进行判断;
也就是说,每一次得到推荐票,都会根据剩余的票来判断此票能否成为leader,能的话就结束,得到Leader

OBSERVER是LOOKING状态时也会调用该方法,不过没用而已,因为非OBSERVER服务器在调用sendNotifications()方法发送消息时,只会发给非OBSERVER,因此OBSERVER取到的n为null,这样就会一直发送消息,确保能连接上;
直到Leader选出来后,会给OBSERVER发送通知,此时n不为null了,进入LEADING的case

8.5

无需选举的情况:如OBSERVING、FOLLOWING、LEADING(选出Leader了),只有LOOKING才需要选举

若一个Server可以接收到n.state为OBSERVEING的通知,说明该Server是刚刚挂掉的Leader,这种场景才符合“正在进行选举过程,且Observer服务器不是LOOKING状态”

注意:Leader挂掉不是宕机,而是失去了大多数follow的连接,变成LOOKING状态,此时会调用lookForLead(),OBServer服务器发过来的状态是OBSERVEING,follow则为LOOKING

FOLLOWING和LEADING则会出现在三种场景下:

1 新的非ObServer主机加入到正常允许的集群,此主机为LOOKING状态,进入lookForLeader()方法,发送消息给其他主机,其他主机都不为LOOKING状态,因此发送各自的状态给此主机,此主机就会收到FOLLOWING或LEADING,代码中进入这两个case(n.electionEpoch != logicalclock.get()

2 Leader挂了,部分Follow感知到,状态变为LOOKING,部分还没有感知到,状态还是为FOLLOWING;
此时这部分告知到的会发送通知给其他的Server,其他Server中那部分没感知到的收到通知后,会回送FOLLOWING状态给部分感知到了的Server,部分感知到了的Server的代码中就会进入这FOLLOWING的case(n.electionEpoch != logicalclock.get(),内部的判断一直返回false)

3 本轮选举中,其他Server已经选举出了新的Leader,但是还没有通知到当前Server,这些已经知道Leader选举完毕的Server变成Follower或Leader,会向当前Server发送LEADING或FOLLOWING通知,代码中进入这两个case(此时n.electionEpoch == logicalclock.get(),内部的判断在outofelection攒够了之后返回true)

​ 第五步三个场景总结:

场景1:进入outofelection的代码,如果当前轮没有返回主机的票,将票都加入到outpfelection了,则会回到开始的while循环重新开始,因为当前主机为LOOKING,所以会进入循环,且因为通知为空,因此会通知其他主机重新发送,下一次while循环就会重新进入此段代码进行判断,此时为第二轮,outpfelection已经齐全了,不用担心传输的问题,可以直接判断,那么LEADING过来时就会过半,返回即可

场景2:LOOKING主机会收到FOLLOWING,不会进入选举,且因为不会收到LEADING,所以不会返回最终选票,即忽视了第五步的代码,一直在while循环中取Notifications;
而FOLLOWING主机会收到LOOKING,进入选举,调用sendNotifications()发送消息,此方法会将状态设置为LOOKING,那么原先的LOOKING主机就会收到LOOKING的主机而不是FOLLOWING,也就进入选举了

场景3:如果传过来的是LEADING,且outofelection中有过半的选票,则通过;否则就将主机加入outofelection,继续取出选票来判断,直到此条件成立

核心代码:将三种情况都解决了

if(n.electionEpoch == logicalclock.get(){//核心1
    if(termPredicate(recvset, new Vote(n.version, n.leader,n.zxid, n.electionEpoch, n.peerEpoch, n.state)) && checkLeader(outofelection, n.leader, n.electionEpoch)//核心2 第一次出现
}
outofelection.put(n.sid, new Vote(n.version, n.leader, n.zxid, n.electionEpoch, n.peerEpoch, n.state));//3
if(termPredicate(recvset, new Vote(n.version, n.leader,n.zxid, n.electionEpoch, n.peerEpoch, n.state)) && checkLeader(outofelection, n.leader, n.electionEpoch)//2 第二次出现

这里要注意网络传输问题,因为Leader可能最先到,进行判断的时候outofelection为空,因此不会返回,等
outofelection过半了之后,leader再到,才能成功

猜你喜欢

转载自blog.csdn.net/qq_41594698/article/details/102581028
今日推荐