4.Nacos一致性协议Raft心跳包发送

Nacos源码分析之发送心跳包源码分析

在RaftCore的init()方法中注册了一个每500ms执行一次的心跳任务

@PostConstruct
    public void init() throws Exception {
       	......
        //开启定时任务,每个500ms发起一次心跳    
        GlobalExecutor.registerHeartbeat(new HeartBeat());
        
        Loggers.RAFT.info("timer started: leader timeout ms: {}, heart-beat timeout ms: {}",
                GlobalExecutor.LEADER_TIMEOUT_MS, GlobalExecutor.HEARTBEAT_INTERVAL_MS);
    }

HeartBeat

这个方法中主要是看是否达到了发送心跳的时间,达到了就走发送心跳的逻辑sendBeat();

//5000ms
public static final long HEARTBEAT_INTERVAL_MS = TimeUnit.SECONDS.toMillis(5L);
//0~5000ms
public volatile long heartbeatDueMs = RandomUtils.nextLong(0, GlobalExecutor.HEARTBEAT_INTERVAL_MS);
//500ms
public static final long TICK_PERIOD_MS = TimeUnit.MILLISECONDS.toMillis(500L);

public class HeartBeat implements Runnable {
    @Override
    public void run() {
        try {

            if (!peers.isReady()) {
                return;
            }
			//获取本机机器的节点信息
            RaftPeer local = peers.local();
            //设置心跳检测的间隔时间,每次发起心跳减去500ms,heartbeatDueMs初始化范围是:0~5000ms
            local.heartbeatDueMs -= GlobalExecutor.TICK_PERIOD_MS;
            if (local.heartbeatDueMs > 0) {
                return;
            }
			//重置心跳时间
            local.resetHeartbeatDue();
			//Leader节点向其他Follow节点小于等于0时发送心跳请求
            sendBeat();
        } catch (Exception e) {
            Loggers.RAFT.warn("[RAFT] error while sending beat {}", e);
        }
    }
}

sendBeat

1.判断如果是单机模式,或者自己不是leader就直接return,因为只有leader才有资格发送心跳包
2.将本机的信息和需要更新的数据的信息封装成一个byte数组,遍历除自己以外的机器发起心跳请求(/v1/ns/raft/beat接口)
3.接收到结果后刷新节点信息

private void sendBeat() throws IOException, InterruptedException {
    //获取本地机器的节点信息
    RaftPeer local = peers.local();
    //判断如果是单机模式,或者自己不是leader就直接return
    if (ApplicationUtils.getStandaloneMode() || local.state != RaftPeer.State.LEADER) {
        return;
    }
    if (Loggers.RAFT.isDebugEnabled()) {
        Loggers.RAFT.debug("[RAFT] send beat with {} keys.", datums.size());
    }
	//重置leader选举时间
    local.resetLeaderDue();

    // build data
    //构建一个空的数据包
    ObjectNode packet = JacksonUtils.createEmptyJsonNode();
    //替换为本地机器的的节点信息
    packet.replace("peer", JacksonUtils.transferToJsonNode(local));

    ArrayNode array = JacksonUtils.createEmptyArrayNode();

    if (switchDomain.isSendBeatOnly()) {
        Loggers.RAFT.info("[SEND-BEAT-ONLY] {}", String.valueOf(switchDomain.isSendBeatOnly()));
    }
	//将信息封装成一个gzip,并且转换为byte数组后发起心跳请求
    if (!switchDomain.isSendBeatOnly()) {
        for (Datum datum : datums.values()) {

            ObjectNode element = JacksonUtils.createEmptyJsonNode();

            if (KeyBuilder.matchServiceMetaKey(datum.key)) {
                element.put("key", KeyBuilder.briefServiceMetaKey(datum.key));
            } else if (KeyBuilder.matchInstanceListKey(datum.key)) {
                element.put("key", KeyBuilder.briefInstanceListkey(datum.key));
            }
            element.put("timestamp", datum.timestamp.get());

            array.add(element);
        }
    }

    packet.replace("datums", array);
    // broadcast
    Map<String, String> params = new HashMap<String, String>(1);
    params.put("beat", JacksonUtils.toJson(packet));

    String content = JacksonUtils.toJson(params);

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    GZIPOutputStream gzip = new GZIPOutputStream(out);
    gzip.write(content.getBytes(StandardCharsets.UTF_8));
    gzip.close();

    byte[] compressedBytes = out.toByteArray();
    String compressedContent = new String(compressedBytes, StandardCharsets.UTF_8);

    if (Loggers.RAFT.isDebugEnabled()) {
        Loggers.RAFT.debug("raw beat data size: {}, size of compressed data: {}", content.length(),
                           compressedContent.length());
    }
	//遍历除自身以外的节点,发送心跳请求
    for (final String server : peers.allServersWithoutMySelf()) {
        try {
            //API_BEAT: /raft/beat
            final String url = buildUrl(server, API_BEAT);
            if (Loggers.RAFT.isDebugEnabled()) {
                Loggers.RAFT.debug("send beat to server " + server);
            }
            HttpClient.asyncHttpPostLarge(url, null, compressedBytes, new AsyncCompletionHandler<Integer>() {
                @Override
                public Integer onCompleted(Response response) throws Exception {
                    if (response.getStatusCode() != HttpURLConnection.HTTP_OK) {
                        Loggers.RAFT.error("NACOS-RAFT beat failed: {}, peer: {}", response.getResponseBody(),
                                           server);
                        MetricsMonitor.getLeaderSendBeatFailedException().increment();
                        return 1;
                    }
					//接收到结果后刷新节点信息
                    peers.update(JacksonUtils.toObj(response.getResponseBody(), RaftPeer.class));
                    if (Loggers.RAFT.isDebugEnabled()) {
                        Loggers.RAFT.debug("receive beat response from: {}", url);
                    }
                    return 0;
                }

                @Override
                public void onThrowable(Throwable t) {
                    Loggers.RAFT.error("NACOS-RAFT error while sending heart-beat to peer: {} {}", server, t);
                    //出现异常时监视器发送心跳失败的次数+1
                    MetricsMonitor.getLeaderSendBeatFailedException().increment();
                }
            });
        } catch (Exception e) {
            Loggers.RAFT.error("error while sending heart-beat to peer: {} {}", server, e);
            MetricsMonitor.getLeaderSendBeatFailedException().increment();
        }
    }

}

RaftController.beat

@PostMapping("/beat")
public JsonNode beat(HttpServletRequest request, HttpServletResponse response) throws Exception {

    String entity = new String(IoUtils.tryDecompress(request.getInputStream()), StandardCharsets.UTF_8);
    String value = URLDecoder.decode(entity, "UTF-8");
    value = URLDecoder.decode(value, "UTF-8");

    JsonNode json = JacksonUtils.toObj(value);

    RaftPeer peer = raftCore.receivedBeat(JacksonUtils.toObj(json.get("beat").asText()));

    return JacksonUtils.transferToJsonNode(peer);
}

RaftCore.receivedBeat

这个方法很长,因为这个方法中不仅有要处理心跳的逻辑,同时还处理了由心跳请求带过来的数据处理的逻辑,所以我们分两段来看,心跳逻辑的处理部分主要做了一下几件事情:
1.进行一系列的判断,看是否出现了异常逻辑(比如发送心跳的不是leader,发送心跳的机器的term比自己的小等等),如果出现了那么抛出异常。

2.排除了异常情况下,走正常逻辑,首先把自己的状态修改为follower,并且把选票改为发送心跳的机器

3.重置leader选举时间和发送心跳的时间

4.更新leader以及peers信息(peers.makeLeader)

这个方法中主要做了以下几件事:
①.设置本地内存的leader为发送心跳的机器
②.判断是否还存在其他状态为leader的节点,如果有,那么发送一个get的异步请求获取该节点的信息(接口为/v1/ns/raft/peer)
③.根据结果对本地的节点信息进行更新

public RaftPeer receivedBeat(JsonNode beat) throws Exception {
    final RaftPeer local = peers.local();
    final RaftPeer remote = new RaftPeer();
    JsonNode peer = beat.get("peer");
    remote.ip = peer.get("ip").asText();
    remote.state = RaftPeer.State.valueOf(peer.get("state").asText());
    remote.term.set(peer.get("term").asLong());
    remote.heartbeatDueMs = peer.get("heartbeatDueMs").asLong();
    remote.leaderDueMs = peer.get("leaderDueMs").asLong();
    remote.voteFor = peer.get("voteFor").asText();
	//如果不是leader发出心跳包 抛出异常
    if (remote.state != RaftPeer.State.LEADER) {
        Loggers.RAFT.info("[RAFT] invalid state from master, state: {}, remote peer: {}", remote.state,
                          JacksonUtils.toJson(remote));
        throw new IllegalArgumentException("invalid state from master, state: " + remote.state);
    }
	//本地任期大于远程任期 抛出异常
    if (local.term.get() > remote.term.get()) {
        Loggers.RAFT
            .info("[RAFT] out of date beat, beat-from-term: {}, beat-to-term: {}, remote peer: {}, and leaderDueMs: {}",
                  remote.term.get(), local.term.get(), JacksonUtils.toJson(remote), local.leaderDueMs);
        throw new IllegalArgumentException(
            "out of date beat, beat-from-term: " + remote.term.get() + ", beat-to-term: " + local.term.get());
    }
	//本地服务状态不是follower 则设置为follower,选票设置为leader的ip
    if (local.state != RaftPeer.State.FOLLOWER) {

        Loggers.RAFT.info("[RAFT] make remote as leader, remote peer: {}", JacksonUtils.toJson(remote));
        // mk follower
        local.state = RaftPeer.State.FOLLOWER;
        local.voteFor = remote.ip;
    }

    final JsonNode beatDatums = beat.get("datums");
    //重置心跳时间和leader选举时间
    local.resetLeaderDue();
    local.resetHeartbeatDue();
	//更新leader以及peers信息
    peers.makeLeader(remote);
	//如果不仅仅只是发送心跳  (可能在心跳的时候也会携带需要更新的数据过来)
    if (!switchDomain.isSendBeatOnly()) {

        Map<String, Integer> receivedKeysMap = new HashMap<>(datums.size());

        for (Map.Entry<String, Datum> entry : datums.entrySet()) {
            receivedKeysMap.put(entry.getKey(), 0);
        }

        // now check datums
        List<String> batch = new ArrayList<>();

        int processedCount = 0;
        if (Loggers.RAFT.isDebugEnabled()) {
            Loggers.RAFT
                .debug("[RAFT] received beat with {} keys, RaftCore.datums' size is {}, remote server: {}, term: {}, local term: {}",
                       beatDatums.size(), datums.size(), remote.ip, remote.term, local.term);
        }
        for (Object object : beatDatums) {
            processedCount = processedCount + 1;

            JsonNode entry = (JsonNode) object;
            String key = entry.get("key").asText();
            final String datumKey;
			//判断是元数据信息还是服务实例数据
            if (KeyBuilder.matchServiceMetaKey(key)) {
                datumKey = KeyBuilder.detailServiceMetaKey(key);
            } else if (KeyBuilder.matchInstanceListKey(key)) {
                datumKey = KeyBuilder.detailInstanceListkey(key);
            } else {
                // ignore corrupted key:
                continue;
            }

            long timestamp = entry.get("timestamp").asLong();

            receivedKeysMap.put(datumKey, 1);

            try {
                //如果本地存在一样的键的数据,时间戳本地的大于远程更新的 那说明数据是过期的,直接忽略
                if (datums.containsKey(datumKey) && datums.get(datumKey).timestamp.get() >= timestamp
                    && processedCount < beatDatums.size()) {
                    continue;
                }
				//如果是本地没有的键的数据或者时间戳大于本地的数据的时间戳的数据,直接添加到list中,等待批量更新
                if (!(datums.containsKey(datumKey) && datums.get(datumKey).timestamp.get() >= timestamp)) {
                    batch.add(datumKey);
                }
				//尽量攒够50一起更新
                if (batch.size() < 50 && processedCount < beatDatums.size()) {
                    continue;
                }

                String keys = StringUtils.join(batch, ",");

                if (batch.size() <= 0) {
                    continue;
                }

                Loggers.RAFT.info("get datums from leader: {}, batch size is {}, processedCount is {}"
                                  + ", datums' size is {}, RaftCore.datums' size is {}", getLeader().ip, batch.size(),
                                  processedCount, beatDatums.size(), datums.size());

                // update datum entry
                //构建http请求根据数据的key获取leader中的数据来更新数据
                //API_GET: /raft/datum
                String url = buildUrl(remote.ip, API_GET) + "?keys=" + URLEncoder.encode(keys, "UTF-8");
                HttpClient.asyncHttpGet(url, null, null, new AsyncCompletionHandler<Integer>() {
                    @Override
                    public Integer onCompleted(Response response) throws Exception {
                        if (response.getStatusCode() != HttpURLConnection.HTTP_OK) {
                            return 1;
                        }

                        List<JsonNode> datumList = JacksonUtils
                            .toObj(response.getResponseBody(), new TypeReference<List<JsonNode>>() {
                            });

                        for (JsonNode datumJson : datumList) {
                            Datum newDatum = null;
                            OPERATE_LOCK.lock();
                            try {

                                Datum oldDatum = getDatum(datumJson.get("key").asText());
								 //旧数据不为空,而且远程的时间戳小于等于本地的时间戳,直接忽略
                                if (oldDatum != null && datumJson.get("timestamp").asLong() <= oldDatum.timestamp
                                    .get()) {
                                    Loggers.RAFT
                                        .info("[NACOS-RAFT] timestamp is smaller than that of mine, key: {}, remote: {}, local: {}",
                                              datumJson.get("key").asText(),
                                              datumJson.get("timestamp").asLong(), oldDatum.timestamp);
                                    continue;
                                }
								//根据不同的数据类型进行处理
                                if (KeyBuilder.matchServiceMetaKey(datumJson.get("key").asText())) {
                                    Datum<Service> serviceDatum = new Datum<>();
                                    serviceDatum.key = datumJson.get("key").asText();
                                    serviceDatum.timestamp.set(datumJson.get("timestamp").asLong());
                                    serviceDatum.value = JacksonUtils
                                        .toObj(datumJson.get("value").toString(), Service.class);
                                    newDatum = serviceDatum;
                                }

                                if (KeyBuilder.matchInstanceListKey(datumJson.get("key").asText())) {
                                    Datum<Instances> instancesDatum = new Datum<>();
                                    instancesDatum.key = datumJson.get("key").asText();
                                    instancesDatum.timestamp.set(datumJson.get("timestamp").asLong());
                                    instancesDatum.value = JacksonUtils
                                        .toObj(datumJson.get("value").toString(), Instances.class);
                                    newDatum = instancesDatum;
                                }

                                if (newDatum == null || newDatum.value == null) {
                                    Loggers.RAFT.error("receive null datum: {}", datumJson);
                                    continue;
                                }
								//本地磁盘写入新数据
                                raftStore.write(newDatum);
								//新数据放入本地内存中
                                datums.put(newDatum.key, newDatum);
                                //添加数据修改的事件
                                notifier.addTask(newDatum.key, ApplyAction.CHANGE);

                                local.resetLeaderDue();
								//更新本地leader和本机的term,数据每次变动,term+100
                                if (local.term.get() + 100 > remote.term.get()) {
                                    getLeader().term.set(remote.term.get());
                                    local.term.set(getLeader().term.get());
                                } else {
                                    local.term.addAndGet(100);
                                }
								 //更新磁盘的term
                                raftStore.updateTerm(local.term.get());

                                Loggers.RAFT.info("data updated, key: {}, timestamp: {}, from {}, local term: {}",
                                                  newDatum.key, newDatum.timestamp, JacksonUtils.toJson(remote), local.term);

                            } catch (Throwable e) {
                                Loggers.RAFT
                                    .error("[RAFT-BEAT] failed to sync datum from leader, datum: {}", newDatum,
                                           e);
                            } finally {
                                OPERATE_LOCK.unlock();
                            }
                        }
                        TimeUnit.MILLISECONDS.sleep(200);
                        return 0;
                    }
                });

                batch.clear();

            } catch (Exception e) {
                Loggers.RAFT.error("[NACOS-RAFT] failed to handle beat entry, key: {}", datumKey);
            }

        }
		//如果是远程没有的key 那么证明本地的key是脏数据 需要删除
        List<String> deadKeys = new ArrayList<>();
        for (Map.Entry<String, Integer> entry : receivedKeysMap.entrySet()) {
            if (entry.getValue() == 0) {
                deadKeys.add(entry.getKey());
            }
        }

        for (String deadKey : deadKeys) {
            try {
                deleteDatum(deadKey);
            } catch (Exception e) {
                Loggers.RAFT.error("[NACOS-RAFT] failed to remove entry, key={} {}", deadKey, e);
            }
        }

    }

    return local;
}

makeLeader

public RaftPeer makeLeader(RaftPeer candidate) {
    //将发送心跳的机器设为leader
    if (!Objects.equals(leader, candidate)) {
        leader = candidate;
        ApplicationUtils.publishEvent(new MakeLeaderEvent(this, leader, local()));
        Loggers.RAFT
            .info("{} has become the LEADER, local: {}, leader: {}", leader.ip, JacksonUtils.toJson(local()),
                  JacksonUtils.toJson(leader));
    }

    for (final RaftPeer peer : peers.values()) {
        Map<String, String> params = new HashMap<>(1);
        //如果本地的内存中还保存着之前的leader
        if (!Objects.equals(peer, candidate) && peer.state == RaftPeer.State.LEADER) {
            try {
                //发送请求获取该机器的信息,更新信息
                String url = RaftCore.buildUrl(peer.ip, RaftCore.API_GET_PEER);
                HttpClient.asyncHttpGet(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] get peer failed: {}, peer: {}", response.getResponseBody(),
                                       peer.ip);
                            peer.state = RaftPeer.State.FOLLOWER;
                            return 1;
                        }

                        update(JacksonUtils.toObj(response.getResponseBody(), RaftPeer.class));

                        return 0;
                    }
                });
            } catch (Exception e) {
                peer.state = RaftPeer.State.FOLLOWER;
                Loggers.RAFT.error("[NACOS-RAFT] error while getting peer from peer: {}", peer.ip);
            }
        }
    }

    return update(candidate);
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值