leader选举
在FastLeaderElection类中有一个lookForLeader()方法,开启一轮选举,无论何时只要我们的QuorumPeer状态变为looking,这个方法就会被调用,这个方法会发生notification给其它同级的服务器,并形成最终的选票(最终的leader),修改自己的状态为leader或者follower,具体步骤:
1. 创建选举对象,做选举前的初始化工作,初始化选票箱、无效选票箱,开始时间投出去多少秒之内回复最小值为200毫秒,之后每次变成上次一的二倍直到为60000固定在这个值
try {
self.jmxLeaderElectionBean = new LeaderElectionBean();
MBeanRegistry.getInstance().register(
self.jmxLeaderElectionBean, self.jmxLocalPeerBean);
} catch (Exception e) {
LOG.warn("Failed to register with JMX", e);
self.jmxLeaderElectionBean = null;
}
if (self.start_fle == 0) {
self.start_fle = Time.currentElapsedTime();
}
try {
// recvset receive set用于存放来自于外部的选票,一个entry代表一次投票
// key为投票者的serviceid ,value为选票
//该集合相当于投票箱
HashMap<Long, Vote> recvset = new HashMap<Long, Vote>();
//outofelection out of election 退出选举
// 其中存放的是非法选票,即投票者的状态不是looking
HashMap<Long, Vote> outofelection = new HashMap<Long, Vote>();
// notTimeout notification Timeout 开始值为200毫秒 最大超时时限为60000毫秒
int notTimeout = finalizeWait;
2. 将自己作为初始leader投出去,修改自己的逻辑时钟,推荐自己为leader传入 zxid、server的id和当前的逻辑时钟,然后通知给所有的同级server告诉别人自己的选择的leader
synchronized(this){
// 逻辑时钟 这个用于改朝换代,在之前的基础上加一,如果选举成功就用这个加一之后的logicalclock
logicalclock.incrementAndGet();
//更新当前的server的推荐信息为自己
// getInitId 获取当前server的id
//getInitLastLoggedZxid 获取当前server最后的也是最大的zxid
//getPeerEpoch 如果是参与选举者,获取当前逻辑时钟currentEpoch,否则是Observer 给它一个最小值0
//updateProposal 这个方法构建推荐的leader,这里推荐的是自己
updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());
}
LOG.info("New election. My id = " + self.getId() +
", proposed zxid=0x" + Long.toHexString(proposedZxid));
//将更新过的信息发送出去,放入队列,遍历所有具有选举权的server发送消息
sendNotifications();
3. 这个while一直循环验证自己和大家的投票谁最适合做leader,直到选出最适合的然后跳出循环,通过一个接受外来通知的链式队列recvqueue获取到外来通知,然后每次取出一个Server,第一 :如果获取的值为空证明没有收到消息,这时就去判断连接管理者是否把所有的消息都投递出去了,只要有server的队列为空,就证明这个消息已经投出去有人接受到了,这时候再重新发送消息,让之前没有响应的再接受一下,如果是false证明所有的都没有收到,那是不是自己与其他的断开连接了,需要重新连接接受别人给自己通知的投票,notTimeout变成之前的两倍,如果一直其他server没有接受到则一直这么循环,最终的notTimeout一直在60000,第二如果不为空证明已经收到消息,先判断改服务和服务所选举的leader是否合法,如果合法继续执行,不合法跳出,就是服务的id在不在选举的列表中。然后进入switch (n.state)方法,如果为looking状态,进入,3.1 之后判断当前的逻辑时钟和server的逻辑时钟是否是同一个,如果是同一个判断server n推荐的leader和当前server的proposedLeader对比,对比逻辑时钟相同,zxid 和 serviceId 如果n大则返回为true,然后将n选举的leader广播出去,表示自己现在重新选择n所选择的leader作为leader,当然这时候自己的接受通知的队列又会接收到所有其他server的通知,比如之前是5 现在就是5+8,然后把n的选择放入选举结果集的map中;3.2如果server n的逻辑时钟大于当前服务的逻辑时钟,证明自己之前的选票都不算术了,清空自己的选票箱,然后判断当前的server和n所选举的server谁更适合做leader,然后更新自己的推荐的leader,广播出去;3.3 逻辑时钟小于当前server的逻辑时钟,证明这个是历史版本了,投票无效,直接丢弃,继续执行下一个。
/*
* Loop in which we exchange notifications until we find a leader
*/
while ((self.getPeerState() == ServerState.LOOKING) &&
(!stop)){
/*
* Remove next notification from queue, times out after 2 times
* the termination time
*/
// receive queue 其中存放着所有接受到的外来的通知
Notification n = recvqueue.poll(notTimeout,
TimeUnit.MILLISECONDS);
/*
* Sends more notifications if haven't received enough.
* Otherwise processes new notification.
*/
if(n == null){
//manager 连接管理者,使用TCP管理,两个同辈Server的通讯并且QuorumCnxManager还管理着这些连接。haveDelivered()只要有一个其他server的队列不为空这个都返回true
if(manager.haveDelivered()){
//重新发送,目的是为了重新接受,获取选票
sendNotifications();
} else {
//重新连接zk集群中的每一个server,
// 连的时候就会接受别人的选票(别人收不齐会一直给他发)
manager.connectAll();
}
/*
* Exponential backoff
*/
int tmpTimeOut = notTimeout*2;
notTimeout = (tmpTimeOut < maxNotificationInterval?
tmpTimeOut : maxNotificationInterval);
LOG.info("Notification time out: " + notTimeout);
}
//验证通知发送者与通知选举的leader是否合法 不能是Observer
else if(validVoter(n.sid) && validVoter(n.leader)) {
/*
* Only proceed if the vote comes from a replica in the
* voting view for a replica in the voting view.
*/
switch (n.state) {
case LOOKING:
// If notification > current, replace and send messages out
//处理当前选举过时的情况;清空票箱,更新逻辑时钟
if (n.electionEpoch > logicalclock.get()) {
logicalclock.set(n.electionEpoch);
//清空票箱
recvset.clear();
//判断当前server与n谁更适合做leader:先判断时代epoch,如果时代相同判断zxid,如果相同再判断servie.id,取大的那一个
// 无论谁更适合,都需要更新当前server推荐信息,然后广播出去
if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
getInitId(), getInitLastLoggedZxid(), getPeerEpoch())) {
updateProposal(n.leader, n.zxid, n.peerEpoch);
} else {
updateProposal(getInitId(),
getInitLastLoggedZxid(),
getPeerEpoch());
}
sendNotifications();
// 处理外来n过时情况,n对于当前选举没有任何用处,直接丢掉 无效选票
} else if (n.electionEpoch < logicalclock.get()) {
if(LOG.isDebugEnabled()){
LOG.debug("Notification election epoch is smaller than logicalclock. n.electionEpoch = 0x"
+ Long.toHexString(n.electionEpoch)
+ ", logicalclock=0x" + Long.toHexString(logicalclock.get()));
}
break;
// totalOrderPredicate 用于判断外来n与当前server所推荐的leader
//处理n.electionEpoch和Logicalclock相等情况
// 谁最适合做新的leader
} else if
// 判断当前 n和推荐的proposedLeader对比,对比逻辑时钟相同,zxid 和 serviceId 如果n大则返回为true,然后将n广播出去,表示自己现在选择n作为leader,否则继续
(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
proposedLeader, proposedZxid, proposedEpoch)) {
// 更新当前server信息
updateProposal(n.leader, n.zxid, n.peerEpoch);
//广播出去 之后自己又会接受到别人的给反馈 之前是5在recvqueue 广播了8个出去现在变成 13个了,这个会一直增加,如果这个成立
sendNotifications();
}
//将外来n通知为一个选票,投放到选票箱
recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));
4. 判断本轮选举是否可以结束了:上面判断完逻辑时钟之后,改投票的也已经投票了,该重新选举的也重新选举了,无效的票也跳出循环了。这时候需要判断自己所推荐的leader在当前server的票箱里投的票是否已经占了一半,这个总的票数在配置文件中配置的,如果已经没有达到跳出switch继续执行,如果已经达到了,继续判断接受的队列里是否还有其它服务推荐的比这个更适合做leader的的,如果有把这个放回到队列,然后跳出这个switch,然后执行第三步,就会把选票清除,然后广播重新来,为什么会出现有的这种情况呢,打个比方,一个8个,你之前的5个已经放入投票箱了,然后第六个来了,它是因为我代码执行到这一步,别人发给我的通知,但是它推荐的leader的zxid比当前的大。如果while条件跳出 说明 n 为null 说明在剩余的通知中没有比当前server所推荐的leader 更合适,然后就清空票箱,修改自己的状态为fllower或者leader状态,等操作,最后返回投票的最终结果,返回票据
//终止断言:当前Server所推荐的leader在票箱里是否过半,如果过半就结束了
//关于过半的配置n 是你提前配置写在配置文件中的,即这个值大于n/2
if (termPredicate(recvset,
new Vote(proposedLeader, proposedZxid,
logicalclock.get(), proposedEpoch))) {
// Verify if there is any change in the proposed leader
while((n = recvqueue.poll(finalizeWait,
TimeUnit.MILLISECONDS)) != null){
//对比队列里是否还有server的zxid、serverId大于当前server所推荐的leader,
if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
proposedLeader, proposedZxid, proposedEpoch)){
//将更适合的n重新放回recqueue 以便对其进行重新投票
// 改循环有两个出口
//break: 从改出口跳出,说明在剩余的通知中找到了更适合做leader的通知 如果有,跳出循环,执行到while然后清空选票箱,通知其他的server,告诉别人自己又换选举对象了
//whiler 条件跳出 说明 n 为null 说明在剩余的通知中没有比当前server所推荐的leader 更合适
recvqueue.put(n);
break;
}
}
/*
* This predicate is true once we don't read any new
* relevant message from the reception queue
*/
//如果n为null 则说明当前server所推荐的leader就是最终的leader
if (n == null) {
//修改当前server的状态,非leader即following
self.setPeerState((proposedLeader == self.getId()) ?
ServerState.LEADING: learningState());
//形成最终选票
Vote endVote = new Vote(proposedLeader,
proposedZxid,
logicalclock.get(),
proposedEpoch);
//清空recvqueue
leaveInstance(endVote);
return endVote;
}
}
break;
5. 无需选举的情况:这里switch 的OBSERVING这个分支不会走,这个就是多余的代码,因为最上层就判断他是不是参与者了
//首先要清楚两点:
//1)当一个Server接收到其它的Server的通知后,无论自己处于什么状态,
// 其都会向那个Server发送自己的通知。
// 2)一个Server若能够接受到其他Server的通知,说明它不是Observer
//而是Participant。因为sendNotification()方法不给Observer发送通知,这里其实也走不到,因为最上层就判断他是不是参与者了
case OBSERVING:
LOG.debug("Notification from observer: " + n.sid);
break;
// 1,2这两种情况都是集群已经选举出leader
// 有两种场景会出现Leader或者follower给当前Server发送通知
// 1)有一个新Server要加入正常运行的集群时,这个新的Server在启动时,
// 其状态为Looking 要查找leader,其向外发通知,此时的Leader、follower的状态肯定不是looking
// 而分别为Leading、follower状态 当 leader 、follower接收到通知后就会向其发送自己的通知,这种情况就相当于 fastPaxos
//2)当其它Server已经在本轮选举中选出了新的leader但他没有通知到当前Server,当前的可能是网络比较慢,接受消息,别人选完了他还没好
// 所以当前Server的状态仍保持为looking 而其它Server中的部分
// 主机状态可能已经是leading或者following了
// 经过分析可知,最终的两种场景是:
// 1)当前server选举的逻辑时钟与其它follower或者leader的epoch相同
// 2)当前server选举的逻辑时钟与其它follower或者leader的epoch不同。
case FOLLOWING:
case LEADING:
/*
* Consider all notifications from the same epoch
* together.
*/
if(n.electionEpoch == logicalclock.get()){
recvset.put(n.sid, new Vote(n.leader,
n.zxid,
n.electionEpoch,
n.peerEpoch));
// 判断当前server选举是否应该退出本轮选举了
// 其首先判断n所推荐的leader在当前Server的票箱中支持率是否过半
// 若过半,再判断n所推荐的leader在outofelection中的状态是否合法,因为已经都是leader状态了所以outofelection这里肯定会有。没有就不合法,继续执行总会走到
// 若合法则可以退出本轮选举
if(ooePredicate(recvset, outofelection, n)) {
//收尾工作
self.setPeerState((n.leader == self.getId()) ?
ServerState.LEADING: learningState());
Vote endVote = new Vote(n.leader,
n.zxid,
n.electionEpoch,
n.peerEpoch);
leaveInstance(endVote);
return endVote;
}
}
/*
* Before joining an established ensemble, verify
* a majority is following the same leader.
*/
outofelection.put(n.sid, new Vote(n.version,
n.leader,
n.zxid,
n.electionEpoch,
n.peerEpoch,
n.state));
//若所推荐的leader在无效选票(来源就是Following和Leading)中所形成的通知集合中的支持率过半,
// 则我就知道谁是leader,就可以退出选举了
if(ooePredicate(outofelection, outofelection, n)) {
synchronized(this){
logicalclock.set(n.electionEpoch);
self.setPeerState((n.leader == self.getId()) ?
ServerState.LEADING: learningState());
}
Vote endVote = new Vote(n.leader,
n.zxid,
n.electionEpoch,
n.peerEpoch);
leaveInstance(endVote);
return endVote;
}
break;
default:
LOG.warn("Notification state unrecognized: {} (n.state), {} (n.sid)",
n.state, n.sid);
break;