3.nacos源码分析之-raft选举

 

找到RaftCore类,该类被@Component标志,并且有个方法被@PostConstruct所标注:

 

@PostConstruct
public void init() throws Exception {

这里有几个核心概念或组件:

1.peer:代表每台nocas机器,记录着一台server的投票相关的元数据信息,比如本机的ip,投票给谁(votefor),AtomicLong类型的term,记录本地服务第几次发起的投票,状体(leader/follower),leader选举间隔时间等。

2.peers:是个RaftPeerSet类型,实际上记录了整个集群所有peer的信息。

3.notifier:一个线程,用作事件通知。

下面看初始化过程:

 

@PostConstruct
public void init() throws Exception {

    Loggers.RAFT.info("initializing Raft sub-system");
    //事件通知线程
    executor.submit(notifier);

    long start = System.currentTimeMillis();
    //从磁盘中加载Datum数据到datums内存中,用于数据恢复
    //Datum:kv对
    //datums:ConcurrentMap<String, Datum>
    raftStore.loadDatums(notifier, datums);
    //loadMeta():加载nacos_home/data/naming/meta.properties文件,默认不存在term这个key,取默认值0
    setTerm(NumberUtils.toLong(raftStore.loadMeta().getProperty("term"), 0L));

    Loggers.RAFT.info("cache loaded, datum count: {}, current term: {}", datums.size(), peers.getTerm());

    while (true) {
        if (notifier.tasks.size() <= 0) {
            break;
        }
        Thread.sleep(1000L);
    }
    initialized = true;

    Loggers.RAFT.info("finish to load data from disk, cost: {} ms.", (System.currentTimeMillis() - start));
    //每500ms调度一下该线程,用来判断是否需要选举leader
    GlobalExecutor.registerMasterElection(new MasterElection());
    GlobalExecutor.registerHeartbeat(new HeartBeat());

    Loggers.RAFT.info("timer started: leader timeout ms: {}, heart-beat timeout ms: {}",
        GlobalExecutor.LEADER_TIMEOUT_MS, GlobalExecutor.HEARTBEAT_INTERVAL_MS);
}

前面先不管,这里最重要的是:GlobalExecutor.registerMasterElection(new MasterElection()); 每过500ms启动一次该选举线程:

 

public static void registerMasterElection(Runnable runnable) {
    //TICK_PERIOD_MS:500
    executorService.scheduleAtFixedRate(runnable, 0, TICK_PERIOD_MS, TimeUnit.MILLISECONDS);
}

传给该方法的MasterElection是个线程,该行实际上是使用一个线程的执行器调起该线程,直接看MasterElection类:

public class MasterElection implements Runnable {
    @Override
    public void run() {
        try {
            //如果还没有初始化完成
            if (!peers.isReady()) {
                return;
            }
            //获取当前机器上跑的这个peer
            RaftPeer local = peers.local();
           //第一次进来:leaderDueMs = leaderDueMs(0-15000毫秒之间的一个随机数) - 500
                //后面由于500ms调度一次,所以每次该线程被调起,会将该leaderDueMs减去TICK_PERIOD_MS(500ms),直到小于0的时候会触发选举
            //后面每次收到一次leader的心跳就会重置leaderDueMs = 15s+(随机0-5s)
            local.leaderDueMs -= GlobalExecutor.TICK_PERIOD_MS;
            //这里用来判断是否参与选举
            if (local.leaderDueMs > 0) {
               
                return;
            }

            // reset timeout
            //重新设置本地的leaderDueMs
            local.resetLeaderDue();
            //设置心跳间隔5s
            local.resetHeartbeatDue();
            //将本地选举投票通过http发送其他几台服务器
            sendVote();
        } catch (Exception e) {
            Loggers.RAFT.warn("[RAFT] error while master election {}", e);
        }

    }

这里最重要的当然就是sendVote()方法了:

 

public void sendVote() {

        RaftPeer local = peers.get(NetUtils.localServer());
        Loggers.RAFT.info("leader timeout, start voting,leader: {}, term: {}",
            JSON.toJSONString(getLeader()), local.term);
        //重置peers,各个peer的voteFor与leader设为null
        peers.reset();
        //选举计数器,记录本地发起的是第几轮选举
        local.term.incrementAndGet();
        //选自己,此时peers中有一个votefor就是自己
        local.voteFor = local.ip;
        //本地server状态设置为CANDIDATE
        local.state = RaftPeer.State.CANDIDATE;

        Map<String, String> params = new HashMap<>(1);
        params.put("vote", JSON.toJSONString(local));
        //将选自己的投票发送给其他servers
        for (final String server : peers.allServersWithoutMySelf()) {
            //API_VOTE接口路径:/raft/vote
            final String url = buildURL(server, API_VOTE);
            try {
                HttpClient.asyncHttpPost(url, null, params, new AsyncCompletionHandler<Integer>() {
                    @Override
                    public Integer onCompleted(Response response) throws Exception {
                        if (response.getStatusCode() != HttpURLConnection.HTTP_OK) {
                            Loggers.RAFT.error("NACOS-RAFT vote failed: {}, url: {}", response.getResponseBody(), url);
                            return 1;
                        }
                        //获取其他server的响应
                        RaftPeer peer = JSON.parseObject(response.getResponseBody(), RaftPeer.class);

                        Loggers.RAFT.info("received approve from peer: {}", JSON.toJSONString(peer));
                        //计算leader
                        peers.decideLeader(peer);

                        return 0;
                    }
                });
            } catch (Exception e) {
                Loggers.RAFT.warn("error while sending vote to server: {}", server);
            }
        }
    }
}

 

该段代码就是自己选自己作为leader,并将该投票发给集群其他所有的server,这里重点关注两个方法:

(1)一个是其他服务端收到该投票后返回了什么信息

(2)一个是收到其他服务端返回的信息后做了什么

首先看第一个问题,服务端接收路径是final String url = buildURL(server, API_VOTE); 可以得到接口路径是:/raft/vote,该接口位于naming这个module下的com.alibaba.nacos.naming.controllers.RaftController,找到该方法:

 

@NeedAuth
@RequestMapping(value = "/vote", method = RequestMethod.POST)
public JSONObject vote(HttpServletRequest request, HttpServletResponse response) throws Exception {

    RaftPeer peer = raftCore.receivedVote(
        JSON.parseObject(WebUtils.required(request, "vote"), RaftPeer.class));

    return JSON.parseObject(JSON.toJSONString(peer));
}

处理逻辑在receivedVote方法中

public RaftPeer receivedVote(RaftPeer remote) {
    if (!peers.contains(remote)) {
        throw new IllegalStateException("can not find peer: " + remote.ip);
    }

    RaftPeer local = peers.get(NetUtils.localServer());
    //本机率先发起的投票,此时本机已经发过投票,那么选本机作为leader
    if (remote.term.get() <= local.term.get()) {
        String msg = "received illegitimate vote" +
            ", voter-term:" + remote.term + ", votee-term:" + local.term;

        Loggers.RAFT.info(msg);
        if (StringUtils.isEmpty(local.voteFor)) {
            local.voteFor = local.ip;
        }

        return local;
    }

    local.resetLeaderDue();
    //如果上面if不成立,说明remote机器率先发起的投票,那么就认同他的投票
    //本地设为follow
    local.state = RaftPeer.State.FOLLOWER;
    local.voteFor = remote.ip;
    local.term.set(remote.term.get());

    Loggers.RAFT.info("vote {} as leader, term: {}", remote.ip, remote.term);

    return local;
}

对于接收端来说,本地代表了接收端,romote代表了发起投票的那一端,这里面有个if判断比较本地term和远程term的逻辑,比如本地term是1,远程也是1,那么说明在接收远程请求之前,本地已经发起过选择自己作为leader的投票,所以将投自己的投票发送出去。如果本地是2,远程是1,说明本地已经发起了下一轮投票,代表了本地先发起的投票,也应该投接收端本地自己。这两种情况都会进入if,返回选择本地的投票。

如果if不成立,说明远程率先发起投票,那就投给远程发起请求的这台机器好了。

现在看第二个问题:(2)一个是收到其他服务端返回的信息后做了什么

 

public RaftPeer decideLeader(RaftPeer candidate) {
    /**
     * 假设3个节点:A,B,C
     * local节点为A,假设A,B,C第一轮同时发起选举请求
     */

    /**
     * 第一轮:
     *  处理B,C节点返回结果:peers{"ip_a":"candidate_a","ip_b":"candidate_b","ip_C":"candidate_C"}
     *
     */
    peers.put(candidate.ip, candidate);

    SortedBag ips = new TreeBag();
    int maxApproveCount = 0;
    String maxApprovePeer = null;

    /**第一轮投票结果:
     * 第一次for循环是a自己的投票:
     * maxApproveCount = 1,maxApprovePeer = A
     *
     * 第二次for循环是B服务器返回的投票,该投票投向B:
     * if (ips.getCount(peer.voteFor) > maxApproveCount) 条件不成立,maxApproveCount = 1,maxApprovePeer = A
     *
     * 第三次for循环是C服务器返回的投票,该投票投向C:
     * if (ips.getCount(peer.voteFor) > maxApproveCount) 条件不成立,maxApproveCount = 1,maxApprovePeer = A
     */
    for (RaftPeer peer : peers.values()) {
        if (StringUtils.isEmpty(peer.voteFor)) {
            continue;
        }

        ips.add(peer.voteFor);

        if (ips.getCount(peer.voteFor) > maxApproveCount) {
            maxApproveCount = ips.getCount(peer.voteFor);
            maxApprovePeer = peer.voteFor;
        }
    }
    //majorityCount():2(假设3个节点)
    //第一轮:maxApproveCount = 1 if条件不成立,返回leader,此时leader为null,没有选举成功
    if (maxApproveCount >= majorityCount()) {
        //找到得票最多的那个peer
        RaftPeer peer = peers.get(maxApprovePeer);
        //设置这个peer为leader
        peer.state = RaftPeer.State.LEADER;

        if (!Objects.equals(leader, peer)) {
            leader = peer;
            applicationContext.publishEvent(new LeaderElectFinishedEvent(this, leader));
            Loggers.RAFT.info("{} has become the LEADER", leader.ip);
        }
    }

    return leader;
}

decideLeader方法,

第一轮投票:假设此时是第一轮投票,假设集群三个节点A,B,C都同时发起选举,那么当三个节点收到其他节点的选取时候,通过对“(1)一个是其他服务端收到该投票后返回了什么信息”的分析,他们都认为是自己先发起的投票,都会将自己返回,那么假设在A节点上,接收来自BC的返回信息后,此时的peers中大概是这样的信息:peers{"ip_a":"candidate_a","ip_b":"candidate_b","ip_C":"candidate_C"}

第一轮得到投票数最多的节点都是1,没有任何一个节点有超出半数的投票,选举失败,返回的leader是null。

第二轮投票:假设A节点率先发起投票,根据“(1)一个是其他服务端收到该投票后返回了什么信息” 的分析,此时A节点的peers大概是这样:{"ip_a":"candidate_a","ip_a":"candidate_a","ip_a":"candidate_a"}

经过for循环后:maxApproveCount = 3,maxApprovePeer = A

过半投票投给了A,后面if条件成立,选举A节点为leader,选举完成!

public RaftPeer decideLeader(RaftPeer candidate) {
    /**
     * 假设3个节点:A,B,C
     * local节点为A,假设A,B,C第一轮同时发起选举请求
     */

    /**
     * 第一轮:
     *  处理B,C节点返回结果:peers{"ip_a":"candidate_a","ip_b":"candidate_b","ip_C":"candidate_C"}
     *
     *  第二轮投票:
     *  处理B,C节点返回结果:peers{"ip_a":"candidate_a","ip_a":"candidate_a","ip_a":"candidate_a"}
     */
    peers.put(candidate.ip, candidate);

    SortedBag ips = new TreeBag();
    int maxApproveCount = 0;
    String maxApprovePeer = null;

    /**第一轮投票:
     * 第一次for循环是a自己的投票:
     * maxApproveCount = 1,maxApprovePeer = A
     *
     * 第二次for循环是B服务器返回的投票,该投票投向B:
     * if (ips.getCount(peer.voteFor) > maxApproveCount) 条件不成立,maxApproveCount = 1,maxApprovePeer = A
     *
     * 第三次for循环是C服务器返回的投票,该投票投向C:
     * if (ips.getCount(peer.voteFor) > maxApproveCount) 条件不成立,maxApproveCount = 1,maxApprovePeer = A
     */

    /**
     * 第二轮投票:
     * maxApproveCount = 3,maxApprovePeer = A
     */
    for (RaftPeer peer : peers.values()) {
        if (StringUtils.isEmpty(peer.voteFor)) {
            continue;
        }

        ips.add(peer.voteFor);

        if (ips.getCount(peer.voteFor) > maxApproveCount) {
            maxApproveCount = ips.getCount(peer.voteFor);
            maxApprovePeer = peer.voteFor;
        }
    }
    //majorityCount():2(假设3个节点)
    //第一轮:maxApproveCount = 1 if条件不成立,返回leader,此时leader为null,没有选举成功
    //第二轮:maxApproveCount = 2 超过半数,选举成功
    if (maxApproveCount >= majorityCount()) {
        //找到得票最多的那个peer
        RaftPeer peer = peers.get(maxApprovePeer);
        //设置这个peer为leader
        peer.state = RaftPeer.State.LEADER;

        if (!Objects.equals(leader, peer)) {
            leader = peer;
            applicationContext.publishEvent(new LeaderElectFinishedEvent(this, leader));
            Loggers.RAFT.info("{} has become the LEADER", leader.ip);
        }
    }

    return leader;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值