图解+源码讲解 Nacos 服务端处理注册请求逻辑

2,260 阅读5分钟

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

图解+源码讲解 Nacos 服务端处理注册请求逻辑

读不在三更五鼓,功只怕一曝十寒 —— 郭沫若 Nacos 源码分析系列相关文章

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

从哪里开始看

    我们先猜猜看,在服务提供者提供服务的时候都是和命名空间有关的代码,所以我们看看命名空间这个naming 项目
image.png

一般处理逻辑都是 controllers 层

我们写项目的时候,controller 都是接收请求的我们看看这个controllers 这个目录里面都有什么
image.png
    果不其然有一个和注册实例相关的 controller 请求,那么我们就去看看都有什么方法这个里面

实例逻辑相关控制类 InstanceController

image.png
    一看里面有很多和实例相关的方法处理逻辑,比如注册、下线、更新、批量更新元数据、获取服务实例信息、心跳、等等核心业务方法,那么我们就先看看注册方法

实例注册方法 InstanceController#register

实例注册请求信息

image.png
image.png
    注册一个新的实例

/**
    注册一个新的实例
*/
@CanDistro
@PostMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String register(HttpServletRequest request) throws Exception {
    // namespaceId 的值是 public
    final String namespaceId = WebUtils
              .optional(request, CommonParams.NAMESPACE_ID, 
                    Constants.DEFAULT_NAMESPACE_ID);
    //serviceName 的值 DEFAULT_GROUP@@service-provider
    final String serviceName = WebUtils.required(request,
                      CommonParams.SERVICE_NAME);
    // 检测服务名字
    NamingUtils.checkServiceNameFormat(serviceName);
    // 解析实例
    final Instance instance = parseInstance(request);
    // 发起注册请求
    getInstanceOperator().registerInstance(namespaceId, serviceName, instance);
    return "ok";
}

解析实例信息

private Instance parseInstance(HttpServletRequest request) throws Exception {
    // DEFAULT_GROUP@@service-provider
    String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
    // 创建 app
    String app = WebUtils.optional(request, "app", "DEFAULT");
    // 解析IP 地址和集群名称
    Instance instance = getIpAddress(request);
    // 设置 app
    instance.setApp(app);
    // 设置服务名称
    instance.setServiceName(serviceName);
    // 自动生成一个实例ID 信息
    // 192.168.60.1#1010#DEFAULT#DEFAULT_GROUP@@service-provider
    instance.setInstanceId(instance.generateInstanceId());
    // 设置服务的当前心跳
    instance.setLastBeat(System.currentTimeMillis());
    // 获取元数据信息
    String metadata = WebUtils.optional(request, "metadata", StringUtils.EMPTY);
    if (StringUtils.isNotEmpty(metadata)) {
        instance.setMetadata(UtilsAndCommons.parseMetadata(metadata));
    }
    instance.validate();
    return instance;// 返回实例信息
}

解析后实例的样子

image.png

发起服务注册

    这里面走的是InstanceOperatorClientImpl#registerInstance 的方法,这个 InstanceOperatorClientImpl 实现了 InstanceOperator 这个接口,这个接口里面定义了一些实例相关的方法,比如注册、下线等操作

@Override
public void registerInstance(String namespaceId, String serviceName,
                             Instance instance) {
    // 是否是短暂的实例信息
    boolean ephemeral = instance.isEphemeral();
    // 获取客户端id 192.168.60.1:1010#true
    String clientId = IpPortBasedClient.getClientId(instance.toInetAddr(), 
                                                    ephemeral);
    // 向 clientManager 中注入客户端,后面会获取进行使用的,其实这就是客户端管理的地方
    createIpPortClientIfAbsent(clientId, ephemeral);
    // 获取服务
    // namespace = public group=DEFAULT_GROUP name=service-provider
    // ephemeral = true 是短暂的实例信息
    Service service = getService(namespaceId, serviceName, ephemeral);
    // 注册实例
    clientOperationService.registerInstance(service, instance, clientId);
}

短暂实例注册类 EphemeralClientOperationServiceImpl

    这个短暂实例就是需要维持心跳续约的实例,不发送心跳就被注册中心自动摘除了,其实客户端就是将自己的客户端信息让入到了ClientManager的子类EphemeralClientOperationServiceImpl类中,之后将自己放到了 publishers 发布者的Map中,之后发布了两个事件给其他订阅者

@Override
public void registerInstance(Service service, Instance instance,
                             String clientId) {
    // 获取服务信息
    // Service{namespace='public', group='DEFAULT_GROUP', 
    // name='service-provider', ephemeral=true, revision=2}
    Service singleton = ServiceManager.getInstance().getSingleton(service);
    // 获取客户端信息获取client实例 192.168.60.1:1010#true,
    // 这个就是上面放入clientManger 中的
    Client client = clientManager.getClient(clientId);
    // 获取发布信息
    // InstancePublishInfo{ip='192.168.60.1', port=1010, healthy=true}
    InstancePublishInfo instanceInfo = getPublishInfo(instance);
    // 将实例信息添加到发布者列表中 ConcurrentHashMap<Service, InstancePublishInfo>
    // publishers 放在这里
    client.addServiceInstance(singleton, instanceInfo);
    // 设置客户端更新时间
    client.setLastUpdatedTime();
    // 发布一个客户端注册事件通知订阅者
    NotifyCenter.publishEvent(
       new ClientOperationEvent.ClientRegisterServiceEvent(singleton, clientId));
    // 发布一个实例元数据事件通知订阅者
    NotifyCenter.publishEvent(
        new MetadataEvent.InstanceMetadataEvent(singleton, 
               instanceInfo.getMetadataId(), false));
}

注册实例存储

    最后服务端有一个后台服务线程,在不断的拉取服务信息放入到 ServiceStorage 这个 serviceDataIndexesMap 中

小结

    其实就是客户端将自己放到了服务器端的 ClientManager的子类EphemeralClientOperationServiceImpl中,这里面有一个Map集合 value 是IpPortBasedClient 类型的,其次将自己在放到发布者Map集合中 ConcurrentHashMap<Service, InstancePublishInfo> publishers ,设置一下更新时间,发布两个事件一个是注册服务事件、一个是实例的元数据事件

其他系列源码分析

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