3. SOFAJRaft源码分析— 是如何进行选举的? (4)

初始化预投票箱是调用了Ballot的init方法进行初始化,分别传入新的集群节点信息,和老的集群节点信息

public boolean init(Configuration conf, Configuration oldConf) { this.peers.clear(); this.oldPeers.clear(); quorum = oldQuorum = 0; int index = 0; //初始化新的节点 if (conf != null) { for (PeerId peer : conf) { this.peers.add(new UnfoundPeerId(peer, index++, false)); } } //设置需要多少票数才能成为leader this.quorum = this.peers.size() / 2 + 1; .... return true; }

我这里为了使逻辑更清晰,假设没有oldConf,省略oldConf相关设置。
这个方法里会遍历所有的peer节点,并将peer封装成UnfoundPeerId插入到peers集合中;然后设置quorum属性,这个属性会在每获得一个投票就减1,当减到0以下时说明获得了足够多的票数,就代表预投票成功。

发起预投票请求 //设置一个回调的类 final OnPreVoteRpcDone done = new OnPreVoteRpcDone(peer, this.currTerm); //向被遍历到的这个节点发送一个预投票的请求 done.request = RequestVoteRequest.newBuilder() // .setPreVote(true) // it's a pre-vote request. .setGroupId(this.groupId) // .setServerId(this.serverId.toString()) // .setPeerId(peer.toString()) // .setTerm(this.currTerm + 1) // next term,注意这里发送过去的任期会加一 .setLastLogIndex(lastLogId.getIndex()) // .setLastLogTerm(lastLogId.getTerm()) // .build(); this.rpcService.preVote(peer.getEndpoint(), done.request, done);

在构造RequestVoteRequest的时候,会将PreVote属性设置为true,表示这次请求是预投票;设置当前节点为ServerId;传给对方的任期是当前节点的任期加一。最后在发送成功收到响应之后会回调OnPreVoteRpcDone的run方法。

OnPreVoteRpcDone#run

public void run(final Status status) { NodeImpl.this.metrics.recordLatency("pre-vote", Utils.monotonicMs() - this.startMs); if (!status.isOk()) { LOG.warn("Node {} PreVote to {} error: {}.", getNodeId(), this.peer, status); } else { handlePreVoteResponse(this.peer, this.term, getResponse()); } }

在这个方法中如果收到正常的响应,那么会调用handlePreVoteResponse方法处理响应

OnPreVoteRpcDone#handlePreVoteResponse

public void handlePreVoteResponse(final PeerId peerId, final long term, final RequestVoteResponse response) { boolean doUnlock = true; this.writeLock.lock(); try { //只有follower才可以尝试发起选举 if (this.state != State.STATE_FOLLOWER) { LOG.warn("Node {} received invalid PreVoteResponse from {}, state not in STATE_FOLLOWER but {}.", getNodeId(), peerId, this.state); return; } if (term != this.currTerm) { LOG.warn("Node {} received invalid PreVoteResponse from {}, term={}, currTerm={}.", getNodeId(), peerId, term, this.currTerm); return; } //如果返回的任期大于当前的任期,那么这次请求也是无效的 if (response.getTerm() > this.currTerm) { LOG.warn("Node {} received invalid PreVoteResponse from {}, term {}, expect={}.", getNodeId(), peerId, response.getTerm(), this.currTerm); stepDown(response.getTerm(), false, new Status(RaftError.EHIGHERTERMRESPONSE, "Raft node receives higher term pre_vote_response.")); return; } LOG.info("Node {} received PreVoteResponse from {}, term={}, granted={}.", getNodeId(), peerId, response.getTerm(), response.getGranted()); // check granted quorum? if (response.getGranted()) { this.prevVoteCtx.grant(peerId); //得到了半数以上的响应 if (this.prevVoteCtx.isGranted()) { doUnlock = false; //进行选举 electSelf(); } } } finally { if (doUnlock) { this.writeLock.unlock(); } } }

这里做了3重校验,我们分别来谈谈:

第一重试校验了当前的状态,如果不是FOLLOWER那么就不能发起选举。因为如果是leader节点,那么它不会选举,只能stepdown下台,把自己变成FOLLOWER后重新选举;如果是CANDIDATE,那么只能进行由FOLLOWER发起的投票,所以从功能上来说,只能FOLLOWER发起选举。
从Raft 的设计上来说也只能由FOLLOWER来发起选举,所以这里进行了校验。

第二重校验主要是校验发送请求时的任期和接受到响应时的任期还是不是一个,如果不是那么说明已经不是上次那轮的选举了,是一次失效的选举

第三重校验是校验响应返回的任期是不是大于当前的任期,如果大于当前的任期,那么重置当前的leader

校验完之后响应的节点会返回一个授权,如果授权通过的话则调用Ballot的grant方法,表示给当前的节点投一票

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zypgxg.html