图解+源码讲解 Nacos 客户端发起心跳请求

1,739 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第27天,点击查看活动详情

图解+源码讲解 Nacos 客户端发起心跳请求

人的才华就如海绵的水,没有外力的挤压,它是绝对流不出来的。流出来后,海绵才能吸收新的源泉 Nacos 源码分析系列相关文章

  1. 从零开始看 Nacos 源码环境搭建
  2. 图解+源码讲解 Nacos 客户端发起注册流程
  3. 图解+源码讲解 Nacos 服务端处理注册请求逻辑
  4. 图解+源码讲解 Nacos 客户端下线流程
  5. 图解+源码讲解 Nacos 服务端处理下线请求
  6. 图解+源码讲解 Nacos 客户端发起心跳请求
  7. 图解+源码讲解 Nacos 服务端处理心跳请求
  8. 图解+源码讲解 Nacos 服务端处理配置获取请求

从哪里开始分析

    其实想想就知道,指定是在客户端发起注册的时候就会在本地有一个后台的线程在进行维护心跳发送请求,那么就是在注册时候指定有一个心跳任务的创建

@Override
public void registerInstance(String serviceName, String groupName, Instance instance) 
    throws NacosException {
    NamingUtils.checkInstanceIsLegal(instance);
    String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
    if (instance.isEphemeral()) {
        // 构建心跳信息
        BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
        // 心跳执行器
        beatReactor.addBeatInfo(groupedServiceName, beatInfo);
    }
    serverProxy.registerService(groupedServiceName, groupName, instance);
}

    这么看就是在进行实例注册的时候,构建的心跳信息,之后放入了心跳执行器的里面进行任务执行

构建心跳信息

    创建心跳信息,里面包含了服务名称、IP地址、端口号、集群名称、权重、元数据信息、心跳发送频率,这里面的心跳发送频率默认是5秒

public BeatInfo buildBeatInfo(String groupedServiceName, Instance instance) {
    // 创建心跳信息
    BeatInfo beatInfo = new BeatInfo();
    // 设置服务名称
    beatInfo.setServiceName(groupedServiceName);
    // 设置IP地址
    beatInfo.setIp(instance.getIp());
    // 设置端口号
    beatInfo.setPort(instance.getPort());
    // 设置集群名称
    beatInfo.setCluster(instance.getClusterName());
    // 设置权重
    beatInfo.setWeight(instance.getWeight());
    // 设置元数据
    beatInfo.setMetadata(instance.getMetadata());
    beatInfo.setScheduled(false);
    // 设置频率
    beatInfo.setPeriod(instance.getInstanceHeartBeatInterval());
    return beatInfo;
}

构建心跳执行器

    创建了一个定时调度线程池执行器,里面创建的是一个后台的守护线程、并且名字是:
com.alibaba.nacos.naming.beat.sender

public BeatReactor(NamingProxy serverProxy, int threadCount) {
    this.serverProxy = serverProxy;
    this.executorService = new ScheduledThreadPoolExecutor(threadCount,
                   new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setDaemon(true);
            thread.setName("com.alibaba.nacos.naming.beat.sender");
            return thread;
        }
    });
}

向心跳执行器中放入心跳信息

public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
    NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
    // 构建key
    String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
    BeatInfo existBeat = null;
    //fix #1733
    if ((existBeat = dom2Beat.remove(key)) != null) {
        existBeat.setStopped(true);
    }
    // 将心跳信息放入 dom2Beat 中
    dom2Beat.put(key, beatInfo);
    // 进行心跳任务创建之后放入调度执行器中执行,默认是5秒
    executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), 
                             TimeUnit.MILLISECONDS);
    // 指标信息监控
    MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}

1. 心跳key的构建

    心跳key = 服务名称+#+ip地址+#+端口号

public String buildKey(String serviceName, String ip, int port) {
    return serviceName + Constants.NAMING_INSTANCE_ID_SPLITTER + 
        ip + Constants.NAMING_INSTANCE_ID_SPLITTER + port;
}

2. 将key和心跳信息放入到dom2Beat Map 中

public final Map<String, BeatInfo> dom2Beat = new ConcurrentHashMap<String, BeatInfo>();

3. 创建心跳任务并执行

class BeatTask implements Runnable {

    BeatInfo beatInfo;
    public BeatTask(BeatInfo beatInfo) {
        this.beatInfo = beatInfo;
    }

    @Override
    public void run() {
        // 心跳是否停止如果是的话就返回
        if (beatInfo.isStopped()) {
            return;
        }
        // 获取心跳频率
        long nextTime = beatInfo.getPeriod();
        try {
            // 发送心跳请求并返回
            JsonNode result = serverProxy.sendBeat(beatInfo,
                    BeatReactor.this.lightBeatEnabled);
            // 从返回的结果里面获取心跳频率
            long interval = result.get("clientBeatInterval").asLong();
            boolean lightBeatEnabled = false;
            if (result.has(CommonParams.LIGHT_BEAT_ENABLED)) {
                lightBeatEnabled = result.get(CommonParams.LIGHT_BEAT_ENABLED)
                    .asBoolean();
            }
            BeatReactor.this.lightBeatEnabled = lightBeatEnabled;
            if (interval > 0) {
                nextTime = interval;
            }
            // code = 10200
            int code = NamingResponseCode.OK;
            // 返回的结果信息里面是否有 code 如果有的话则获取
            if (result.has(CommonParams.CODE)) {
                code = result.get(CommonParams.CODE).asInt();
            }
            // 如果没有找到请求资源 20404那么就创建一个实例进行注册
            if (code == NamingResponseCode.RESOURCE_NOT_FOUND) {
                // 创建一个实例
                Instance instance = new Instance();
                // 设置端口号
                instance.setPort(beatInfo.getPort());
                // 设置IP
                instance.setIp(beatInfo.getIp());
                // 设置权重
                instance.setWeight(beatInfo.getWeight());
                // 设置元数据信息
                instance.setMetadata(beatInfo.getMetadata());
                // 设置集群名称
                instance.setClusterName(beatInfo.getCluster());
                // 设置服务名称
                instance.setServiceName(beatInfo.getServiceName());
                // 设置实例ID
                instance.setInstanceId(instance.getInstanceId());
                // 设置是虚拟节点
                instance.setEphemeral(true);
                try {
                    // 发起服务注册
                    serverProxy.registerService(beatInfo.getServiceName(),
                       NamingUtils.getGroupName(beatInfo.getServiceName()), instance);
                } catch (Exception ignore) {
                }
            }
        } 
        // 心跳任务执行
        executorService.schedule(new BeatTask(beatInfo), nextTime, 
                                 TimeUnit.MILLISECONDS);
    }
}

心跳请求发起

    serverProxy.sendBeat(beatInfo,BeatReactor.this.lightBeatEnabled) 这句代码进行了心跳发送请求,请求的路径是:http://localhost:8848/nacos/v1/ns/instance/beat

public JsonNode sendBeat(BeatInfo beatInfo, boolean lightBeatEnabled) 
    throws NacosException {

    if (NAMING_LOGGER.isDebugEnabled()) {
        NAMING_LOGGER.debug("[BEAT] {} sending beat to server: {}",
                            namespaceId, beatInfo.toString());
    }
    // 构建参数进行请求
    Map<String, String> params = new HashMap<String, String>(8);
    Map<String, String> bodyMap = new HashMap<String, String>(2);
    if (!lightBeatEnabled) {
        bodyMap.put("beat", JacksonUtils.toJson(beatInfo));
    }
    //放入一些参数,比如名称空间等
    params.put(CommonParams.NAMESPACE_ID, namespaceId);
    params.put(CommonParams.SERVICE_NAME, beatInfo.getServiceName());
    params.put(CommonParams.CLUSTER_NAME, beatInfo.getCluster());
    params.put("ip", beatInfo.getIp());
    params.put("port", String.valueOf(beatInfo.getPort()));
    // 发起请求
    // 请求路径是 http://localhost:8848/nacos/v1/ns/instance/beat
    String result = reqApi(UtilAndComs.nacosUrlBase + "/instance/beat",
                    params, bodyMap, HttpMethod.PUT);
    return JacksonUtils.toObj(result);
}

小结

    其实就是在客户端实例进行注册的时候,创建了心跳信息 BeatInfo ,之后将这个心跳信息放入了一个dom2Beat Map中,之后又创建了一个心跳任务,并将其放入了定时调度器进行周期执行心跳请求的访问

其他系列源码分析

feign 源码分析系列相关文章

  1. 图解+源码讲解 Feign 如何将客户端注入到容器中
  2. 图解+源码讲解动态代理获取 FeignClient 代理对象
  3. 图解+源码讲解代理对象 ReflectiveFeign 分析
  4. 图解+源码讲解 Feign 如何选取指定服务
  5. 图解+源码讲解 Feign 请求的流程
    ribbon 源码分析系列相关文章
  6. Ribbon 原理初探
  7. 图解+源码讲解 Ribbon 如何获取注册中心的实例
  8. 图解+源码讲解 Ribbon 服务列表更新
  9. 图解+源码讲解 Ribbon 服务选择原理
  10. 图解+源码讲解 Ribbon 如何发起网络请求 eureka 源码分析系列相关文章
  11. eureka-server 项目结构分析
  12. 图解+源码讲解 Eureka Server 启动流程分析
  13. 图解+源码讲解 Eureka Client 启动流程分析
  14. 图解+源码讲解 Eureka Server 注册表缓存逻辑
  15. 图解+源码讲解 Eureka Client 拉取注册表流程
  16. 图解+源码讲解 Eureka Client 服务注册流程
  17. 图解+源码讲解 Eureka Client 心跳机制流程
  18. 图解+源码讲解 Eureka Client 下线流程分析
  19. 图解+源码讲解 Eureka Server 服务剔除逻辑
  20. 图解+源码讲解 Eureka Server 集群注册表同步机制