Nacos 服务注册概述及客户端注册实例源码分析(一)

Nacos 服务注册与发现概述

Nacos 核心功能点

服务注册: Nacos Client 会通过发送 REST 请求的方式向 Nacos Server 注册自己的服务,提供自身的元数据,比如 IP 地址、端口等信息,Nacos Server 接收到注册请求以后,就会把这些元数据信息存储在一个双层的内存 Map 中

服务心跳: 在服务注册后,Nacos Client 会维护一个定时心跳来持续通知 Nacos Server,说明服务一直处于可用状态,防止被剔除,默认 5s 发送一次心跳

服务健康检查: Nacos Server 会开启一个定时任务来检查注册服务实例的健康情况,对于超过 15s 没有收到客户端心跳会将它的 healthy 属性设置为 false「客户端服务发现时不会发现」,如果某个实例超过 30s 没有收到心跳,直接剔除该实例「被剔除的实例如果恢复发送心跳则会重新注册」

服务发现: 服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个 REST 请求给 Nacos Server,获取上面注册的服务清单,并且缓存在 Nacos Client 本地,同时会在 Nacos Client 本地开启一个定时任务来定时拉取服务端最新的注册表信息更新到本地缓存中

服务同步: Nacos Server 集群之间会互相同步服务实例,来保证服务信息的一致性

Nacos 服务端及客户端模块图

Nacos 服务注册概述及客户端注册实例源码分析(一)_第1张图片
如上图,核心模块集中在 nacos-console、name-naming、nacos-config 中

Nacos 客户端服务注册源码入口分析

Nacos GitHub 地址
Nacos 源码,本文在 nacos-2.1.1 版本进行分析

服务注册信息

从 nacos-client 模块中开始说起,说起客户端就必然涉及到服务注册,先了解一下 Nacos 客户端会传递什么信息给到服务端侧,我们直接从 nacos-client 项目的 NamingTest 类说起:

public class NamingTest {
    @Test
    public void testServiceList() throws Exception {
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.SERVER_ADDR, "127.0.0.1:8848");
        properties.put(PropertyKeyConst.USERNAME, "nacos");
        properties.put(PropertyKeyConst.PASSWORD, "nacos");
        Instance instance = new Instance();
        instance.setIp("1.1.1.1");
        instance.setPort(800);
        instance.setWeight(2);
        Map<String, String> map = new HashMap<String, String>();
        map.put("netType", "external");
        map.put("version", "2.0");
        instance.setMetadata(map);
        NamingService namingService = NacosFactory.createNamingService(properties);
        namingService.registerInstance("nacos.test.1", instance);
        ThreadUtils.sleep(5000L);
        List<Instance> list = namingService.getAllInstances("nacos.test.1");
        System.out.println(list);
        ThreadUtils.sleep(30000L);
        //        ExpressionSelector expressionSelector = new ExpressionSelector();
        //        expressionSelector.setExpression("INSTANCE.metadata.registerSource = 'dubbo'");
        //        ListView serviceList = namingService.getServicesOfServer(1, 10, expressionSelector);
    }
}

其实这就是客户端注册的一个 Test 类,它模仿了一个真实的服务注册进了 Nacos 的过程,包括 NacosServer 连接、实例的创建、实例属性的赋值、注册实例,所以在这个其中包括了服务注册的核心代码;仅从此处的代码分析,可以看出,Nacos 注册服务实例时,包含了两大类信息:Nacos Server 连接信息和实例信息

Nacos Server 连接信息

Nacos Server 连接信息,存储在 Properties 当中,包含以下信息:

  • Server 地址:Nacos 服务器地址,属性 Key 为 serverAddr
  • 用户名:连接 Nacos 服务用户名,属性 Key 为 username,默认值为 nacos
  • 密码:连接 Nacos 服务密码,属性 Key 为 password,默认值为 nacos

实例信息

注册实例信息用 Instance 对象承载,注册的实例信息又分为两部分:实例基础信息、元数据
实例基础信息

  • instanceId:实例的唯一 ID
  • ip:实例 IP,提供给消费者进行通信的地址
  • port:端口,提供给消费者访问的端口
  • weight:权重,当前实例的权限,浮点类型(默认为 1.0D)
  • healthy:健康状况,默认 true
  • enabled:实例是否准备好接收请求,默认 true
  • ephemeral:实例是否为瞬时的,默认为 true,缓存在内存中,还未持久化入库的
  • clusterName:实例所属的集群名称
  • serviceName:实例的服务信息

Instance 类包含了实例的基础信息之外,还包含了用于存储元数据的 metadata「描述数据的数据」类型为 HashMap,从当前这个 Demo 中我们可以得知存放了两个数据:

  • netType:顾名思义,网络类型:值为 external,也就是外网的意思
  • version:版本,Nacos 版本,这里是 2.0 版本

除了 Demo 中这些 “自定义” 信息,在 Instance 类中还定义了一些默认信息,这些信息通过 get 方法提供:

// 心跳的间隔时间默认值 5s
public long getInstanceHeartBeatInterval() {
  return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_INTERVAL,
                                     Constants.DEFAULT_HEART_BEAT_INTERVAL);
}

// 心跳超时时间默认值 15s
public long getInstanceHeartBeatTimeOut() {
  return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_TIMEOUT,
                                     Constants.DEFAULT_HEART_BEAT_TIMEOUT);
}

// IP 删除超时时间默认值 30s
public long getIpDeleteTimeout() {
  return getMetaDataByKeyWithDefault(PreservedMetadataKeys.IP_DELETE_TIMEOUT,
                                     Constants.DEFAULT_IP_DELETE_TIMEOUT);
}

// 实例 ID 生成器默认值:simple
public String getInstanceIdGenerator() {
  return getMetaDataByKeyWithDefault(PreservedMetadataKeys.INSTANCE_ID_GENERATOR,
                                     Constants.DEFAULT_INSTANCE_ID_GENERATOR);
}

上面的 get 方法在需要元数据默认值时会被使用到:

  • preserved.heart.beat.interval:心跳间隔 Key,默认值为 5s,也就是默认 5s 进行一次心跳
  • preserved.heart.beat.timeout:心跳超时 Key,默认值为 15s,也就是默认 15s 收不到心跳,实例将会标记为不健康
  • preserved.ip.delete.timeout:实例 IP 被删除 Key,默认值为 30s,也就是 30s 收不到心跳,实例将会被移除
  • preserved.instance.id.generator:实例 ID 生成器 Key,默认值为 simple

这些都是 Nacos 提供的默认值,也就是当前实例注册时会告知 Nacos Server 说:我的心跳间隔、心跳超时等对应的值是多少,按照这个值来判断我这个实例是否健康

有了这些信息,基本上已经知道注册实例时需要传递什么参数、需要配置什么参数了

NamingService 接口

NamingService 接口是 Nacos 命名服务对外提供的一个统一接口,看对应的源码可以发现,它提供了大量实例相关的接口方法

  • 注册服务实例,提供了多个不同参数的重载方法,可以指定组名、集群名

    // 注册服务实例,指定 IP、Port
    void registerInstance(String serviceName, String ip, int port) throws NacosException
    
  • 注销服务实例

    void deregisterInstance(String serviceName, String ip, int port) throws NacosException
    
  • 获取全部的服务实例

    List<Instance> getAllInstances(String serviceName) throws NacosException
    
  • 获取健康的服务实例

    List<Instance> selectInstances(String serviceName, boolean healthy) throws NacosException
    
  • 获取集群中健康的服务实例

    List<Instance> selectInstances(String serviceName, List<String> clusters, boolean healthy) throws NacosException
    
  • 使用负载均衡策略选择一个健康的服务实例

    Instance selectOneHealthyInstance(String serviceName) throws NacosException
    
  • 订阅服务事件

    void subscribe(String serviceName, EventListener listener) throws NacosException
    
  • 取消订阅服务事件

    void unsubscribe(String serviceName, EventListener listener) throws NacosException
    
  • 获取所有(或指定)服务名称

    ListView<String> getServicesOfServer(int pageNo, int pageSize, ...) throws NacosException
    
  • 获取所有订阅的服务

    List<ServiceInfo> getSubscribeServices() throws NacosException
    
  • 获取 Nacos 服务状态

    String getServerStatus()
    
  • 主动关闭服务

    void shutDown() throws NacosException
    

这些方法中提供了大量的重载方法,应用于不同场景、不同类型实例或服务的筛选,所以我们只需要在不同的情况下使用不同的方法即可
NamingService 实例化是通过 NamingFactory 类和上面的 Nacos 服务信息,从以下代码中可以看出这里采用了反射机制来实例化 NamingService,具体的实现类为 NacosNamingService:

public static NamingService createNamingService(Properties properties) throws NacosException {
  try {
    Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
    Constructor constructor = driverImplClass.getConstructor(Properties.class);
    return (NamingService) constructor.newInstance(properties);
  } catch (Throwable e) {
    throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
  }
}

NacosNamingService 实现

在示例代码中使用了 NamingService#registerInstance 方法来进行服务实例的注册,该方法接收两个参数:服务名称和实例对象;这个方法的最大作用是设置了当前实例的分组信息;在 Nacos 中,通过 Namespace、Group、Service、Cluster 等一层层的将实例进行环境的隔离;在这里设置了默认的分组名:DEFAULT_GROUP

public void registerInstance(String serviceName, Instance instance) throws NacosException {
  registerInstance(serviceName, Constants.DEFAULT_GROUP, instance);
}

紧接着调用的 registerInstance 方法如下,这个方法做了两件事情:

public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
  NamingUtils.checkInstanceIsLegal(instance);
  clientProxy.registerService(serviceName, groupName, instance);
}

1、检查心跳时间设置的是否正确(心跳默认值是 5s)

public static void checkInstanceIsLegal(Instance instance) throws NacosException {
  // 实例的心跳间隔必须小于 "心跳超时" 和 "ip删除超时"
  if (instance.getInstanceHeartBeatTimeOut() < instance.getInstanceHeartBeatInterval()
      || instance.getIpDeleteTimeout() < instance.getInstanceHeartBeatInterval()) {
    throw new NacosException(NacosException.INVALID_PARAM,
                             "Instance 'heart beat interval' must less than 'heart beat timeout' and 'ip delete timeout'.");
  }
  // 实例的集群名称不满足条件:只支持数字和字母
  if (!StringUtils.isEmpty(instance.getClusterName()) && !CLUSTER_NAME_PATTERN.matcher(instance.getClusterName()).matches()) {
    throw new NacosException(NacosException.INVALID_PARAM,String.format("Instance 'clusterName' should be characters with only 0-9a-zA-Z-. (current: %s)",
                                           instance.getClusterName()));
  }
}

2、通过 NamingClientProxy 代理类来执行服务注册操作
通过 clientProxy 属性可以发现 NamingClientProxy 这个代理接口的具体实现是由 NamingClientProxyDelegate 来完成的,这个可以直接从 NacosNamingService 构造方法看出,在 init 方法中进行初始化操作:

public NacosNamingService(Properties properties) throws NacosException {
  init(properties);
}

private void init(Properties properties) throws NacosException {
  ValidatorUtils.checkInitParam(properties);
  this.namespace = InitUtils.initNamespaceForNaming(properties);
  InitUtils.initSerialization();
  InitUtils.initWebRootContext(properties);
  initLogName(properties);

  this.notifierEventScope = UUID.randomUUID().toString();
  this.changeNotifier = new InstancesChangeNotifier(this.notifierEventScope);
  NotifyCenter.registerToPublisher(InstancesChangeEvent.class, 16384);
  NotifyCenter.registerSubscriber(changeNotifier);
  this.serviceInfoHolder = new ServiceInfoHolder(namespace, this.notifierEventScope, properties);
  this.clientProxy = new NamingClientProxyDelegate(this.namespace, serviceInfoHolder, properties, changeNotifier);
}

NamingClientProxyDelegate 实现

继续追踪 NamingClientProxyDelegate#registerService 方法的具体实现,代码如下:

public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
  getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);
}

真正调用注册服务方并不是代理实现类,而是通过当前实例是否为瞬时对象,来选择对应的客户端代理进行请求:

private NamingClientProxy getExecuteClientProxy(Instance instance) {
  return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;
}

如果当前实例为瞬时对象,则采用 gRPC 协议(NamingGrpcClientProxy)进行请求,否则采用(NamingHttpClientProxy)进行请求

private boolean ephemeral = true;

默认为瞬时对象,也就是说,2.0 版本默认采用了 gRPC 协议与 Nacos 服务进行交互

NamingGrpcClientProxy 实现

主要关注一下 registerService 方法的实现,主要作了以下两件事情:
1、缓存当前注册的实例信息用于恢复,缓存的数据结构为 ConcurrentMap:Key->groupName@@serviceName,value->前面封装的实例信息「Instance、groupName、serviceName」
2、另外一件事情就是封装了具体的参数,基于 gRPC 进行服务的调用和结果的处理

public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
  NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance {}", namespaceId, serviceName,
                     instance);
  // 缓存数据
  redoService.cacheInstanceForRedo(serviceName, groupName, instance);
  // 基于 gRPC 进行服务的调用
  doRegisterService(serviceName, groupName, instance);
}

public void doRegisterService(String serviceName, String groupName, Instance instance) throws NacosException {
  // 封装好请求信息
  InstanceRequest request = new InstanceRequest(namespaceId, serviceName, groupName,
                                                NamingRemoteConstants.REGISTER_INSTANCE, instance);
  // 发送请求服务端进行注册
  requestToServer(request, Response.class);
  // 标识该服务已被注册
  redoService.instanceRegistered(serviceName, groupName);
}

Nacos 入口源码流程图

Nacos 服务注册概述及客户端注册实例源码分析(一)_第2张图片

Nacos 客户端实例注册源码案例分析

实际上我们在真实的生产的环境中,若让某个服务注册到 Nacos 中,首先需要引入依赖:

<dependency>
    <groupId>com.alibaba.cloudgroupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>

在引入这个依赖之后,找到 spring-boot 自动装配文件:META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration,\
  com.alibaba.cloud.nacos.ribbon.RibbonNacosAutoConfiguration,\
  com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpointAutoConfiguration,\
  com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration,\
  com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration,\
  com.alibaba.cloud.nacos.discovery.reactive.NacosReactiveDiscoveryClientConfiguration,\
  com.alibaba.cloud.nacos.discovery.configclient.NacosConfigServerAutoConfiguration,\
  com.alibaba.cloud.nacos.NacosServiceAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
  com.alibaba.cloud.nacos.discovery.configclient.NacosDiscoveryClientConfigServiceBootstrapConfiguration
org.springframework.context.ApplicationListener=\
  com.alibaba.cloud.nacos.discovery.logging.NacosLoggingListener

然后再 spring-boot 自动装配的功能,自动装配的源码实现可以看博主博客:SpringBoot 自动装配流程及源码剖析
首先找到加载 EnbaleAutoConfiguration 对应的类,然后在这里我们就能看见很多 Nacos 相关的内容,一般这种文件都会找 Auto 关键字的文件来进行查看,然后我们要现在要了解的是客户端注册,所以我们应该要找的是:NacosServiceRegistryAutoConfiguration

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = {"spring.cloud.service-registry.auto-registration.enabled"},matchIfMissing = true)
@AutoConfigureAfter({AutoServiceRegistrationConfiguration.class, AutoServiceRegistrationAutoConfiguration.class, NacosDiscoveryAutoConfiguration.class})
public class NacosServiceRegistryAutoConfiguration {
    public NacosServiceRegistryAutoConfiguration() {
    }

    @Bean
    public NacosServiceRegistry nacosServiceRegistry(NacosServiceManager nacosServiceManager, NacosDiscoveryProperties nacosDiscoveryProperties) {
        return new NacosServiceRegistry(nacosServiceManager, nacosDiscoveryProperties);
    }

    @Bean
    @ConditionalOnBean({AutoServiceRegistrationProperties.class})
    public NacosRegistration nacosRegistration(ObjectProvider<List<NacosRegistrationCustomizer>> registrationCustomizers, NacosDiscoveryProperties nacosDiscoveryProperties, ApplicationContext context) {
        return new NacosRegistration((List)registrationCustomizers.getIfAvailable(), nacosDiscoveryProperties, context);
    }

    @Bean
    @ConditionalOnBean({AutoServiceRegistrationProperties.class})
    public NacosAutoServiceRegistration nacosAutoServiceRegistration(NacosServiceRegistry registry, AutoServiceRegistrationProperties autoServiceRegistrationProperties, NacosRegistration registration) {
        return new NacosAutoServiceRegistration(registry, autoServiceRegistrationProperties, registration);
    }
}

在此类中有很多的 Bean 组件,这些都是 spring 容器启动时自动进行注入的,一般情况下我们可能会看每一个 Bean 组件初始化时具体作了什么,但是实际上这里最核心的是 NacosAutoServiceRegistration 类,注入它时还需要三个参数,这三个参数会提前被加载,尤其是 NacosServiceRegistry 这个参数后续中也会用到

NacosAutoServiceRegistration

Nacos 服务注册概述及客户端注册实例源码分析(一)_第3张图片
通过以上的类图可以看出,NacosAutoServiceRegistration 继承自 AbstractAutoServiceRegistration,而这个类型又实现了 ApplicationListener 接口,一般只要实现了 ApplicationListener 接口的,只需要关注它的 onApplicationEvent 方法的实现逻辑,它会帮我们完成一些初始化加载的工作,该方法是在项目启动时-容器进行初始化时进行调用的

public void onApplicationEvent(WebServerInitializedEvent event) {
  this.bind(event);
}

@Deprecated
public void bind(WebServerInitializedEvent event) {
  ApplicationContext context = event.getApplicationContext();
  if (!(context instanceof ConfigurableWebServerApplicationContext) || !"management".equals(((ConfigurableWebServerApplicationContext)context).getServerNamespace())) {
    this.port.compareAndSet(0, event.getWebServer().getPort());
    this.start();
  }
}

最终在 start 方法中调用 register 方法进行服务注册

public void start() {
  if (!this.isEnabled()) {
    if (logger.isDebugEnabled()) {
      logger.debug("Discovery Lifecycle disabled. Not starting");
    }
  } else {
    if (!this.running.get()) {
      this.context.publishEvent(new InstancePreRegisteredEvent(this, this.getRegistration()));
      this.register();
      if (this.shouldRegisterManagement()) {
        this.registerManagement();
      }

      this.context.publishEvent(new InstanceRegisteredEvent(this, this.getConfiguration()));
      this.running.compareAndSet(false, true);
    }
  }
}

ServiceRegistry#register

分析到这里,可以得知真实的服务注册的入口和具体调用那个方法来注册

protected void register() {
  this.serviceRegistry.register(this.getRegistration());
}

但 serviceRegistry 实际上是一个接口,它的具体实现类是 NacosServiceRegistry,到这里就与之前分析到的源码连接在一起了,因为在这里调用了之前讲过的 registerInstance 实例注册方法,查看该类下的 register 方法

public void register(Registration registration) {
  if (StringUtils.isEmpty(registration.getServiceId())) {
    log.warn("No service to register for nacos client...");
  } else {
    NamingService namingService = this.namingService();
    String serviceId = registration.getServiceId();
    String group = this.nacosDiscoveryProperties.getGroup();
    // 构建 Instance 实例
    Instance instance = this.getNacosInstanceFromRegistration(registration);
    try {
	  // 向服务端注册此客户端
      namingService.registerInstance(serviceId, group, instance);
      log.info("nacos registry, {} {} {}:{} register finished", new Object[]{group, serviceId, instance.getIp(), instance.getPort()});
    } catch (Exception var7) {
      if (this.nacosDiscoveryProperties.isFailFast()) {
        log.error("nacos registry, {} register failed...{},", new Object[]{serviceId, registration.toString(), var7});
        ReflectionUtils.rethrowRuntimeException(var7);
      } else {
        log.warn("Failfast is false. {} register failed...{},", new Object[]{serviceId, registration.toString(), var7});
      }
    }
  }
}

接下来就是从已经分析过的链路进行调用了,如下:

NamingClientProxyDelegate#registerService—>NamingGrpcClientProxy#registerService
此段源码在标题「Nacos 客户端服务注册源码入口分析」分析过,在这不作过多阐述

NamingGrpcClientProxy#requestToServer

发送请求服务端进行注册实例,在此处会涉及到 rpcClient#request 方法调用

private <T extends Response> T requestToServer(AbstractNamingRequest request, Class<T> responseClass) throws NacosException {
  try {
    request.putAllHeader(this.getSecurityHeaders(request.getNamespace(), request.getGroupName(), request.getServiceName()));
    Response response = this.requestTimeout < 0L ? this.rpcClient.request(request) : this.rpcClient.request(request, this.requestTimeout);
    if (ResponseCode.SUCCESS.getCode() != response.getResultCode()) {
      throw new NacosException(response.getErrorCode(), response.getMessage());
    }
    if (responseClass.isAssignableFrom(response.getClass())) {
      return response;
    }
    LogUtils.NAMING_LOGGER.error("Server return unexpected response '{}', expected response should be '{}'", response.getClass().getName(), responseClass.getName());
  } catch (Exception var4) {
    throw new NacosException(500, "Request nacos server failed: ", var4);
  }
  throw new NacosException(500, "Server return invalid response");
}

此 RpcClient 对象是在 NamingGrpcClientProxy 构造函数中进行初始化的,随即会调用其下 start 方法启动,在里面会调用 connectToServer(serverInfo) 方法「主要操作:通过地址+端口来初始化 gRPC 连接信息后进行建立连接」

public NamingGrpcClientProxy(String namespaceId, SecurityProxy securityProxy, ServerListFactory serverListFactory, Properties properties, ServiceInfoHolder serviceInfoHolder) throws NacosException {
  super(securityProxy);
  this.namespaceId = namespaceId;
  this.uuid = UUID.randomUUID().toString();
  this.requestTimeout = Long.parseLong(properties.getProperty("namingRequestTimeout", "-1"));
  Map<String, String> labels = new HashMap();
  labels.put("source", "sdk");
  labels.put("module", "naming");
  this.rpcClient = RpcClientFactory.createClient(this.uuid, ConnectionType.GRPC, labels);
  this.redoService = new NamingGrpcRedoService(this);
  this.start(serverListFactory, serviceInfoHolder);
}

private void start(ServerListFactory serverListFactory, ServiceInfoHolder serviceInfoHolder) throws NacosException {
  this.rpcClient.serverListFactory(serverListFactory);
  this.rpcClient.registerConnectionListener(this.redoService);
  this.rpcClient.registerServerRequestHandler(new NamingPushRequestHandler(serviceInfoHolder));
  this.rpcClient.start();
  NotifyCenter.registerSubscriber(this);
}

随即在 requestToServer 方法中调用 rpcClient#request 方法就可以将客户端实例注册进去了!!!

通过 Open API 注册实例

Nacos 官网提供了基于 RestFul API 方式的接口给我们去进行客户端实例的注册
官网地址:Nacos 注册实例-Open API
Nacos 服务注册概述及客户端注册实例源码分析(一)_第4张图片
通过 postman 方式进行调用,来测试是否通过该请求可以实现客户端注册效果:

curl --location --request POST 'http://127.0.0.1:8848/nacos/v1/ns/instance' \
--form 'port="8848"' \
--form 'ip="127.0.0.1"' \
--form 'serviceName="nacos.test"'

或在项目中将 ephemeral 属性设置为 false,也可以实现通过 http 方法进行调用.
ephemeral=true 代表基于 gRPC,ephemeral=false 代表基于 Http

spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        ephemeral: false

然后可以运行有引入该依赖的项目,通过 Debug 方式运行,增加如下断点,验证流程是否按正常的来走:

  • NacosServiceRegistry#register
  • NacosNamingService#registerInstance

随后会调用 NamingClientProxy 的实现类 NamingHttpClientProxy#registerService 方法,源码如下:

public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
    LogUtils.NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", new Object[]{this.namespaceId, serviceName, instance});
    String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
    if (instance.isEphemeral()) {
        BeatInfo beatInfo = this.beatReactor.buildBeatInfo(groupedServiceName, instance);
        this.beatReactor.addBeatInfo(groupedServiceName, beatInfo);
    }

    Map<String, String> params = new HashMap(32);
    params.put("namespaceId", this.namespaceId);
    params.put("serviceName", groupedServiceName);
    params.put("groupName", groupName);
    params.put("clusterName", instance.getClusterName());
    params.put("ip", instance.getIp());
    params.put("port", String.valueOf(instance.getPort()));
    params.put("weight", String.valueOf(instance.getWeight()));
    params.put("enable", String.valueOf(instance.isEnabled()));
    params.put("healthy", String.valueOf(instance.isHealthy()));
    params.put("ephemeral", String.valueOf(instance.isEphemeral()));
    params.put("metadata", JacksonUtils.toJson(instance.getMetadata()));
    this.reqApi(UtilAndComs.nacosUrlInstance, params, "POST");
    // UtilAndComs.nacosUrlInstance == /nacos/v1/ns/instance
}

在这里会把实例信息先放到散列表中,然后调用 reqApi 方法来发送请求,接口:/nacos/v1/ns/instance

区分版本

spring-cloud-alibaba-version:2.2.8 版本使用的是 Nacos 2.1.1,默认采用 gRPC 调用;2.2.5.RELEASE 版本使用的是 Nacos 1.4.1,采用的是 Http 调用

总结客户端注册实例流程图

Nacos 服务注册概述及客户端注册实例源码分析(一)_第5张图片

结尾

欢迎大家在评论框分享您的看法,喜欢该文章帮忙给个赞和收藏,感谢!!
分享个人学习源码的几部曲

  • 设计模式掌握为前提,程序员的内功修炼法,不分语言
  • 不要太追究于细节,捋清大致脉路即可;太过于追究于细节,你会越捋越乱
  • 关注重要的类和方法、核心逻辑
  • 掌握 Debug 技巧,在关键的类和方法多停留,多作分析和记录

更多技术文章可以查看:vnjohn 个人博客

你可能感兴趣的:(Nacos,java,微服务,spring,cloud)