图解+源讲解 Nacos 服务端处理下线请求

1,969 阅读4分钟

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

图解+源讲解 Nacos 服务端处理下线请求

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

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

从哪里开始看起

    指定是服务端接收到客户端的请求才进行下线处理的,那么客户端的请求地址是什么呢?客户端下线流程中已经表明了,请求路径就是:/nacos/v1/ns/instance,一看是这个路径那么就知道是哪个controller下面的了,指定是和instance 注册是一个类里面的,果不其然就是这里面的
image.png

处理入口 InstanceController#deregister

@CanDistro
@DeleteMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String deregister(HttpServletRequest request) throws Exception {
    // 根据请求获取实例信息
    Instance instance = getIpAddress(request);
    // 获取命名空间Id信息
    String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID,
                                           Constants.DEFAULT_NAMESPACE_ID);
    // 获取服务名称
    String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
    NamingUtils.checkServiceNameFormat(serviceName);
    //进行服务下线处理
    getInstanceOperator().removeInstance(namespaceId, serviceName, instance);
    return "ok";
}

获取实例信息

    从请求中解析出来下线的实例信息


private Instance getIpAddress(HttpServletRequest request) {
    String enabledString = WebUtils.optional(request, "enabled", StringUtils.EMPTY);
    boolean enabled;
    if (StringUtils.isBlank(enabledString)) {
        enabled = BooleanUtils.toBoolean(WebUtils.optional(request, "enable", "true"));
    } else {
        enabled = BooleanUtils.toBoolean(enabledString);
    }
    // 权重
    String weight = WebUtils.optional(request, "weight", "1");
    // 健康状态
    boolean healthy = BooleanUtils.toBoolean(
        WebUtils.optional(request, "healthy", "true"));
    // 获取基础服务实例信息
    Instance instance = getBasicIpAddress(request);
    // 设置实例权重
    instance.setWeight(Double.parseDouble(weight));
    // 设置实例的健康状态
    instance.setHealthy(healthy);
    instance.setEnabled(enabled);

    return instance;
}

// 获取基础服务实例信息
private Instance getBasicIpAddress(HttpServletRequest request) {
    // IP
    final String ip = WebUtils.required(request, "ip");
    // 端口号
    final String port = WebUtils.required(request, "port");
    // 集群名称,这里是 default
    String cluster = WebUtils.optional(request, CommonParams.CLUSTER_NAME, 
                                       StringUtils.EMPTY);
    if (StringUtils.isBlank(cluster)) {
        cluster = WebUtils.optional(request, "cluster", 
                                    UtilsAndCommons.DEFAULT_CLUSTER_NAME);
    }
    // 是否是虚拟节点
    boolean ephemeral = BooleanUtils.toBoolean(
        WebUtils.optional(request, "ephemeral", 
                          String.valueOf(switchDomain.isDefaultInstanceEphemeral())));
    // 构建实例信息
    Instance instance = new Instance();
    // 设置端口号
    instance.setPort(Integer.parseInt(port));
    // 设置IP地址
    instance.setIp(ip);
    // 是否是虚拟节点
    instance.setEphemeral(ephemeral);
    // 设置集群名称
    instance.setClusterName(cluster);
    //返回实例信息
    return instance;
}

服务下线处理 getInstanceOperator().removeInstance

@Override
public void removeInstance(String namespaceId, String serviceName, Instance instance) {
    // 是否是临时节点
    boolean ephemeral = instance.isEphemeral();
    // 获取 clientId 信息
    String clientId = IpPortBasedClient.getClientId(instance.toInetAddr(), ephemeral);
    if (!clientManager.contains(clientId)) {
        Loggers.SRV_LOG.warn("remove instance from non-exist client: {}", clientId);
        return;
    }
    // 获取服务信息
    Service service = getService(namespaceId, serviceName, ephemeral);
    // 下线实例
    clientOperationService.deregisterInstance(service, instance, clientId);
}

下线实例 clientOperationService#deregisterInstance

    将当前下线实例信息,从发布者集合中移除出去,之后发布事件告知其他订阅者

@Override
public void deregisterInstance(Service service, Instance instance, String clientId) {
    if (!ServiceManager.getInstance().containSingleton(service)) {
        Loggers.SRV_LOG.warn("remove instance from non-exist service: {}", service);
        return;
    }
    // 就是从客户端管理器中获取实例出来
    Service singleton = ServiceManager.getInstance().getSingleton(service);
    Client client = clientManager.getClient(clientId);
    //  服务下线的时候从发布者中将实例移除
    InstancePublishInfo removedInstance = client.removeServiceInstance(singleton);
    // 设置一下客户端更新事件
    client.setLastUpdatedTime();
    if (null != removedInstance) {
        // 发布两个事件,客户端下线事件、实例元数据事件
        NotifyCenter.publishEvent(
            new ClientOperationEvent.ClientDeregisterServiceEvent(singleton, clientId));
        NotifyCenter.publishEvent(
            new MetadataEvent.InstanceMetadataEvent(singleton,
                                                removedInstance.getMetadataId(), true));
    }
}

小结

    其实主要就是做了两件事件,一个是将发布者中的当前下线实例移除掉。之后再告诉其他订阅者一声我下线了,别订阅我了就完事了。

其他系列源码分析

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 集群注册表同步机制