Nacos 进阶篇---Nacos服务端怎么维护不健康的微服务实例 ?(七)

一、引言

  在 Nacos 后台管理服务列表中,我们可以看到微服务列表,其中有一栏叫“健康实例数”    (如下图),表示对应的客户端实例信息是否可用状态。

 

那Nacos服务端是怎么感知客户端的状态是否可用呢 ?

本章重点:

  • 实例心跳接口做了哪些事情 ?
  • 服务端是怎么维护不健康的实例的,怎么下线不健康实例的,做了哪些操作 ?

二、目录     

目录

一、引言

二、目录        

三、服务端实例心跳接口源码分析

四、服务端实例心跳健康检查定时任务源码分析

五、总结


   

三、服务端实例心跳接口源码分析

主线任务:实例心跳接口做了哪些事情 ?

 在客户端服务发起注册的时候 (在第二章节),会开启一个心跳任务,每5s发送一次健康心跳检查,告诉服务端我这个服务还活着。(前面已经讲过

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()));
    
    // 发送实例心跳接口请求
    String result = reqApi(UtilAndComs.nacosUrlBase + "/instance/beat", params, bodyMap, HttpMethod.PUT);
    return JacksonUtils.toObj(result);
}

服务端接受到实例心跳接口,会现在内存注册表中找 Instance,如果找不到会重新注册。然后提交一个 clientBeatProcessor 异步任务,更改 lastBeat 属性

@CanDistro
@PutMapping("/beat")
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public ObjectNode beat(HttpServletRequest request) throws Exception {

    // 省略部分代码

    // 获取请求参数namespaceId、serviceName
    String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
    String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
    NamingUtils.checkServiceNameFormat(serviceName);
    Loggers.SRV_LOG.debug("[CLIENT-BEAT] full arguments: beat: {}, serviceName: {}", clientBeat, serviceName);

    // 通过namespaceId、serviceName、ip、port、clusterName 从内存注册表当中获取对应的 Instance 实例对象
    Instance instance = serviceManager.getInstance(namespaceId, serviceName, clusterName, ip, port);

    // 如果 instance 为空,那么会重新注册
    if (instance == null) {
        if (clientBeat == null) {
            result.put(CommonParams.CODE, NamingResponseCode.RESOURCE_NOT_FOUND);
            return result;
        }

        instance = new Instance();
        instance.setPort(clientBeat.getPort());
        instance.setIp(clientBeat.getIp());
        instance.setWeight(clientBeat.getWeight());
        instance.setMetadata(clientBeat.getMetadata());
        instance.setClusterName(clusterName);
        instance.setServiceName(serviceName);
        instance.setInstanceId(instance.getInstanceId());
        instance.setEphemeral(clientBeat.isEphemeral());

        // 这里调用重新注册的方法
        serviceManager.registerInstance(namespaceId, serviceName, instance);
    }

    // 通过namespaceId、serviceName获取对应的 Service
    Service service = serviceManager.getService(namespaceId, serviceName);

    if (service == null) {
        throw new NacosException(NacosException.SERVER_ERROR,
                "service not found: " + serviceName + "@" + namespaceId);
    }
    if (clientBeat == null) {
        clientBeat = new RsInfo();
        clientBeat.setIp(ip);
        clientBeat.setPort(port);
        clientBeat.setCluster(clusterName);
    }

    // 重点:开启异步任务,更改 lastBeat 属性
    service.processClientBeat(clientBeat);
    
    // 省略部分代码
    return result;
}

接着往下看重点 service.processClientBeat() 任务,这个方法会开启一个异步任务,异步任务的话肯定会有run 方法,那我们直接看 clientBeatProcessor 对象中的 run 方法

public void processClientBeat(final RsInfo rsInfo) {
    ClientBeatProcessor clientBeatProcessor = new ClientBeatProcessor();
    clientBeatProcessor.setService(this);
    clientBeatProcessor.setRsInfo(rsInfo);
    // 立即执行
    HealthCheckReactor.scheduleNow(clientBeatProcessor);
}

在异步任务当中,首先会获取当前节点下所有的临时实例,然后通过 ip+port 找到当前 instance,然后把 instance 中的 lastBeat属性更改为当前时间,并且如果 该 instance 为不健康状态,更改为健康状态

public class ClientBeatProcessor implements Runnable {

    public static final long CLIENT_BEAT_TIMEOUT = TimeUnit.SECONDS.toMillis(15);

    private RsInfo rsInfo;

    private Service service;

    @JsonIgnore
    public PushService getPushService() {
        return ApplicationUtils.getBean(PushService.class);
    }

    public RsInfo getRsInfo() {
        return rsInfo;
    }

    public void setRsInfo(RsInfo rsInfo) {
        this.rsInfo = rsInfo;
    }

    public Service getService() {
        return service;
    }

    public void setService(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        Service service = this.service;
        if (Loggers.EVT_LOG.isDebugEnabled()) {
            Loggers.EVT_LOG.debug("[CLIENT-BEAT] processing beat: {}", rsInfo.toString());
        }

        // 本小节重点方法
        // 获取当前 ip、clusterName
        String ip = rsInfo.getIp();
        String clusterName = rsInfo.getCluster();
        int port = rsInfo.getPort();
        Cluster cluster = service.getClusterMap().get(clusterName);
        // 获取当前 cluster 下所有的临时实例
        List<Instance> instances = cluster.allIPs(true);

        // 遍历临时实例
        for (Instance instance : instances) {
            // 通过判断ip、port,确认是否是当前 instance 的实例
            if (instance.getIp().equals(ip) && instance.getPort() == port) {
                if (Loggers.EVT_LOG.isDebugEnabled()) {
                    Loggers.EVT_LOG.debug("[CLIENT-BEAT] refresh beat: {}", rsInfo.toString());
                }
                // 把 lastBeat属性更改为当前时间
                instance.setLastBeat(System.currentTimeMillis());
                if (!instance.isMarked()) {
                    // 如果 instance 为不健康状态,更改为健康状态
                    if (!instance.isHealthy()) {
                        instance.setHealthy(true);
                        Loggers.EVT_LOG
                                .info("service: {} {POS} {IP-ENABLED} valid: {}:{}@{}, region: {}, msg: client beat ok",
                                        cluster.getService().getName(), ip, port, cluster.getName(),
                                        UtilsAndCommons.LOCALHOST_SITE);
                        getPushService().serviceChanged(service);
                    }
                }
            }
        }
    }
}

小结

     首先在 客户端服务发起注册的时候 (在第二章节),会开启一个心跳任务,每5s发送一次健康心跳检查,告诉服务端我这个服务还活着。(前面已经讲过)

    那么服务端接受到了 实例心跳接口的请求,会现在内存注册表中找 Instance,如果找不到会重新注册。然后提交一个 clientBeatProcessor 异步任务,在异步任务当中,首先会找到当前集群下的所有临时实例,然后通过 ip +port 找到当前instance 实例,把当前instance 中的 lastBeat属性更改为当前时间,如果 instance 为不健康状态,更改为健康状态,到此实例心跳接口就结束了。

四、服务端实例心跳健康检查定时任务源码分析

主线任务:服务端是怎么维护不健康的实例的,怎么下线不健康实例的,做了哪些操作 ?

     这块代码是在服务端 register(注册)接口当中的,之前分析过 register 注册逻辑,因为这块是分支代码,前面没细看。

   我们来看下 createEmptyService 这个方法了,里面有个异步任务,作用就是:检查有哪些客户端是不健康的状态,如果不健康就需要对它进行处理

public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {

    // 不知道是创建了一个什么服务
    createEmptyService(namespaceId, serviceName, instance.isEphemeral());

    // 根据namespaceId、serviceName获取 Service服务
    Service service = getService(namespaceId, serviceName);

    // service为空就抛出异常
    if (service == null) {
        throw new NacosException(NacosException.INVALID_PARAM,
                "service not found, namespace: " + namespaceId + ", service: " + serviceName);
    }

    // 上面都是分支代码
    // 主线任务:添加服务实例
    addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}

我们直接看重点代码,直接跳到开启异步任务这里。上面的代码流程:createEmptyService()-> createServiceIfAbsent()-> putServiceAndInit(service) -> service.init();

public void init() {
    // 开启异步延时任务 clientBeatCheckTask ,每5s执行一次
    HealthCheckReactor.scheduleCheck(clientBeatCheckTask);
    for (Map.Entry<String, Cluster> entry : clusterMap.entrySet()) {
        entry.getValue().setService(this);
        entry.getValue().init();
    }
}

本章重点,开启了一个 clientBeatCheckTask 异步任务。

@Override
public void run() {
    try {
        // 本章重点
        // 获取全部临时实例
        List<Instance> instances = service.allIPs(true);

        for (Instance instance : instances) {
            // 当前时间 - instance中 lastBeat属性时间  > 15s
            if (System.currentTimeMillis() - instance.getLastBeat() > instance.getInstanceHeartBeatTimeOut()) {
                if (!instance.isMarked()) {
                    if (instance.isHealthy()) {
                        // 如果这个 instance 实例还是健康状态,就更改为 "不健康状态"!
                        instance.setHealthy(false);
                        Loggers.EVT_LOG
                                .info("{POS} {IP-DISABLED} valid: {}:{}@{}@{}, region: {}, msg: client timeout after {}, last beat: {}",
                                        instance.getIp(), instance.getPort(), instance.getClusterName(),
                                        service.getName(), UtilsAndCommons.LOCALHOST_SITE,
                                        instance.getInstanceHeartBeatTimeOut(), instance.getLastBeat());
                        // 事件发布监听事件,通过 upd 协议发送通知
                        getPushService().serviceChanged(service);
                        ApplicationUtils.publishEvent(new InstanceHeartbeatTimeoutEvent(this, instance));
                    }
                }
            }
        }

        if (!getGlobalConfig().isExpireInstance()) {
            return;
        }

        // 这里还是遍历 临时实例
        for (Instance instance : instances) {

            if (instance.isMarked()) {
                continue;
            }

            // 当前时间 - instance中 lastBeat属性时间  > 30s
            if (System.currentTimeMillis() - instance.getLastBeat() > instance.getIpDeleteTimeout()) {
                Loggers.SRV_LOG.info("[AUTO-DELETE-IP] service: {}, ip: {}", service.getName(),
                        JacksonUtils.toJson(instance));
                // 直接从注册表中删除当前 instance
                deleteIp(instance);
            }
        }

    } catch (Exception e) {
        Loggers.SRV_LOG.warn("Exception while processing client beat time out.", e);
    }

}

小结:

  • 第一个循环的作用,为了筛选出不健康的 Instance 实例,并且把 Instance 中的 healthy  属性改为 false。那么怎么筛选出不健康的实例的 ?利用的就是 Instance 中的 lastBeat 属性。如果是健康的实例,那么客户端就会每5s调一次实例心跳接口,更新 lastBeat 属性为当前时间。如果是不健康的实例,那么 Instance 实例 中的 lastBeat 属性是不会变化的,一旦 lastBeat 跟当前时间比超过 15s,就会被认定为不健康的实例。
  • 第二个循环的作用,找出那些 Instance 是需要删除的,如果 lastBeat 跟当前时间比超过 30s,Nacos 会把该 Instance 从注册表当中进行删除。
五、总结

总结:

     本章讲了Nacos怎么维护整个微服务实例健康状态的流程,在客户端发起注册服务时会有心跳任务,每5s给服务端发送一次心态,服务端会把该 Instance 实例中的lastBeat 属性更新为当前时间。并且在服务端实例注册的时候,会开启心跳健康检查任务,把 lastBeat 跟当前时间比超过 15s,就会被标识为不健康的实例,把lastBeat 跟当前时间比超过 30s,Nacos 会把该 Instance 从注册表当中进行删除

最后的最后,别忘了把源码分析图补充完整: 

  • 29
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: nacos-server-2.0.2.zip是Nacos的一个版本,Nacos是阿里巴巴开源的一个动态服务发现、配置管理和服务管理平台。它为云原生应用提供了服务注册、配置管理和服务发现等功能。 nacos-server-2.0.2.zip是Nacos 2.0.2版本的压缩文件,包含了Nacos服务器的安装包。安装Nacos服务器可以帮助开发人员搭建自己的服务注册与发现平台,提供了方便的管理界面和API接口。 Nacos 2.0.2版本是Nacos的一个更新版本,相比于旧版本,它可能包含了一些新的功能、性能改进和 bug 修复。安装Nacos服务器需要将该压缩文件解压,并按照指导进行配置和启动。安装完成后,可以通过Web界面或API接口进行服务的注册、发现和配置的管理。 使用Nacos服务器可以实现微服务架构中的服务注册与发现,可以解决服务间相互调用的问题。通过Nacos的配置管理功能,可以实现动态配置的管理和更新,提供了灵活的配置选项和快速的配置变更能力。此外,Nacos还提供了服务管理相关的功能,如健康检查、多集群支持等。 总之,nacos-server-2.0.2.zip 是Nacos 2.0.2版本的安装包,安装并配置 Nacos 服务器后,可以实现服务注册、发现、配置管理和服务管理等功能,为云原生应用开发提供了方便的工具和平台。 ### 回答2: nacos-server-2.0.2.zip是Nacos服务端的一个版本。Nacos是一个开源的服务发现、配置管理和动态DNS服务的平台,用于帮助开发人员更好地构建和管理微服务架构Nacos-server-2.0.2.zip包是Nacos的安装包,其中包含了Nacos服务端的所有必要文件和组件。通过下载并解压该包,我们可以得到一个可用的Nacos服务端实例,可以用于搭建自己的服务发现和配置管理平台。 在Nacos-server-2.0.2.zip中,主要包含以下几个关键部分: 1. nacos-server.jar:这是Nacos服务端的核心程序,负责处理服务注册、发现、配置管理等核心功能。 2. conf目录:该目录下包含了Nacos的配置文件,可以通过修改这些配置文件来进行个性化定制,以满足具体需求。 3. bin目录:该目录下包含了Nacos服务端的启动脚本,可以通过执行这些脚本来启动、停止、重启Nacos服务。 使用Nacos-server-2.0.2.zip时,我们可以根据具体需求对Nacos进行配置和使用。首先,我们需要在conf目录下修改nacos.properties文件,配置Nacos的监听地址、数据库信息等。然后,通过执行bin目录下的startup.sh(Linux/Mac)或startup.cmd(Windows)脚本,即可启动Nacos服务。 一旦Nacos服务启动成功,我们可以通过HTTP请求或使用Nacos提供的Java SDK来进行服务注册、发现和配置管理等操作。Nacos-server-2.0.2.zip提供了一个稳定且功能齐全的Nacos服务端实例,可以满足日常开发和生产使用中的需求。 ### 回答3: nacos-server-2.0.2.zip是Nacos的一个版本。Nacos是阿里巴巴开源的一个动态服务发现、配置管理和服务管理平台。Nacos支持多种编程语言,如Java、Go、Python等,并提供了丰富的API接口,方便开发者在各种场景中使用。 Nacos-server-2.0.2.zip中包含了Nacos Server的安装包。要使用Nacos,首先需要下载并解压此压缩包。解压后,可以得到Nacos Server的所有相关文件和目录。 Nacos Server是Nacos的核心组件,它提供了服务发现、配置管理和服务管理的功能。通过Nacos Server,用户可以注册和发现服务,实现分布式系统中服务的动态发现。同时,Nacos Server还支持配置管理,可以将应用程序的配置信息集中管理,使得配置的更新和变更更加方便。 使用Nacos Server,用户可以实现微服务架构中的动态服务发现与管理,使得服务间的通信更加灵活和可靠。此外,Nacos Server还提供了一些其他的功能,如健康检查、权重调整等,用于提升系统的可用性和性能。 总之,nacos-server-2.0.2.zip提供了Nacos Server的安装包,用户可以通过安装和配置Nacos Server来实现服务发现、配置管理和服务管理等功能,帮助开发者更好地构建和管理分布式系统。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逸航不吃瓜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值