实战 Cloud : Nacos Client 端主流程

1,871 阅读3分钟

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca…

一 . 前言

Nacos 请求时部分操作异常会直接宕掉 ,这个时候了解整个 Nacos Client 端的流程就至关重要.

Nacos 主流程主要分为以下几个部分 :

  • Nacos Client 端的启动和初始化
  • Nacos Client 端服务注册
  • Nacos Client 端服务发现

二 . Nacos Client 端的启动和初始化

Nacos 的自动装配类主要分为2个部分 :

  • NacosConfigAutoConfiguration
  • NacosDiscoveryAutoConfiguration

上一篇 Nacos 配置加载流程和优先级 已经看了配置的处理流程 , 这一篇主要关注 NacosDiscoveryAutoConfiguration

2.1 Nacos 的 Discovery 自动装配类

在 Nacos 的启动过程中 , 在配置类中初始化了如下几个对象 :

- new NacosServiceManager() 
- new NacosServiceDiscovery(discoveryProperties, nacosServiceManager) : 从缓存中获取列表
- new NacosDiscoveryClient(nacosServiceDiscovery)

2.1.1 NacosWatch 的处理

// C- NacosWatch
public void start() {
    
   if (this.running.compareAndSet(false, true)) {
       
       // Step 1 : 构建 EventListener 对象
      EventListener eventListener = listenerMap.computeIfAbsent(buildKey(),
            event -> new EventListener() {
               @Override
               public void onEvent(Event event) {
                  if (event instanceof NamingEvent) {
                  
                     List<Instance> instances = ((NamingEvent) event).getInstances();
                     
                     Optional<Instance> instanceOptional = selectCurrentInstance(instances);
                     
                     instanceOptional.ifPresent(currentInstance -> {
                        resetIfNeeded(currentInstance);
                     });
                  }
               }
            });

    
      // Step 2 : 通过 NacosProperties 准备 NamingService 对象 ->  2.1.2 Step 2
      NamingService namingService = nacosServiceManager
            .getNamingService(properties.getNacosProperties());
      try {
      
         namingService.subscribe(properties.getService(), properties.getGroup(),
               Arrays.asList(properties.getClusterName()), eventListener);
      }
      catch (Exception e) {
         log.error("namingService subscribe failed, properties:{}", properties, e);
      }

      this.watchFuture = this.taskScheduler.scheduleWithFixedDelay(
            this::nacosServicesWatch, this.properties.getWatchDelay());
   }
}

2.1.2 初始化流程

Step1 : NacosWatch 发起监听

// NamingService init 流程
private void init(Properties properties) throws NacosException {
    ValidatorUtils.checkInitParam(properties);
    this.namespace = InitUtils.initNamespaceForNaming(properties);
    InitUtils.initSerialization();
    initServerAddr(properties);
    // 初始化web容器
    InitUtils.initWebRootContext();
    // 初始化缓存目录
    initCacheDir();
    // 初始化 log 路径
    initLogName(properties);
    
    this.eventDispatcher = new EventDispatcher();
    // 代理对象用于调用远程 Server
    this.serverProxy = new NamingProxy(this.namespace, this.endpoint, this.serverList, properties);
    // 用于向Nacos服务端发送已注册服务的心跳
    this.beatReactor = new BeatReactor(this.serverProxy, initClientBeatThreadCount(properties));
    // HostReactor用于获取、保存、更新各Service实例信息
    this.hostReactor = new HostReactor(this.eventDispatcher, this.serverProxy, beatReactor, this.cacheDir,
            isLoadCacheAtStart(properties), initPollingThreadCount(properties));
}

Step 2 : NamingFactory 构建 NamingService

// 实际上是通过反射生产最终的实例对象
Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
Constructor constructor = driverImplClass.getConstructor(Properties.class);
NamingService vendorImpl = (NamingService) constructor.newInstance(properties);

// 工作空间
private String namespace;
//
private String endpoint;
// 服务列表对应的 Server地址 : localhost:8848
private String serverList;
// 本地缓存地址
private String cacheDir;
// log 名称 : 通常是 naming.log
private String logName;

private HostReactor hostReactor;
private BeatReactor beatReactor;

private EventDispatcher eventDispatcher;
private NamingProxy serverProxy;

Step 3 : EventDispatcher 添加监听器

public void subscribe(String serviceName, String groupName, List<String> clusters, EventListener listener)
        throws NacosException {
        
    // 通过监听器来实现更新Service
    eventDispatcher.addListener(hostReactor
                    .getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ",")),
            StringUtils.join(clusters, ","), listener);
}


public void addListener(ServiceInfo serviceInfo, String clusters, EventListener listener) {
    
    // 构建一个 EventListener 集合 
    List<EventListener> observers = Collections.synchronizedList(new ArrayList<EventListener>());
    observers.add(listener);
    
    // ConcurrentMap<String, List<EventListener>> observerMap
    // 其中有个 Notifier 的线程 , 通过 while 循环持续的处理实例
    // /nacos/v1/ns/instance
    observers = observerMap.putIfAbsent(ServiceInfo.getKey(serviceInfo.getName(), clusters), observers);
    if (observers != null) {
        observers.add(listener);
    }
    
    // change 时刷新 Service
    serviceChanged(serviceInfo);
}

hostReactor.getServiceInfo 结果 : image.png

三 . Nacos Client 端服务注册

服务注册涉及到如下流程 :

  • NacosRegistration:保存服务的基本数据信息
  • NacosServiceRegistry:实现服务注册
  • NacosServiceRegistryAutoConfiguration:Nacos自动配置类

Nacos 服务注册的起点是 @EnableDiscoveryClient , 其最终会调用 NacosAutoServiceRegistration :

  • AbstractAutoServiceRegistration
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {
   // 默认为 true , 开启后就会调用对应的 AutoServiceRegistration
   boolean autoRegister() default true;
}

3.1 start

不同的注册中心 , 会有不同的实现类 ,此处是对应 Nacos 的 NacosAutoServiceRegistration

// C- NacosAutoServiceRegistration
public void start() {
   // 如果未开启 , 则直接return 
   
   
   // only initialize if nonSecurePort is greater than 0 and it isn't already running
   // because of containerPortInitializer below
   if (!this.running.get()) {
      this.context.publishEvent(new InstancePreRegisteredEvent(this, getRegistration()));
      
      // 调用 Registry 发起注册 : this.serviceRegistry.register(getRegistration());
      register();
      
      if (shouldRegisterManagement()) {
         registerManagement();
      }
      this.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration()));
      this.running.compareAndSet(false, true);
   }

}

3.2 NacosServiceRegistry 发起注册

public void register(Registration registration) {

   if (StringUtils.isEmpty(registration.getServiceId())) {
      return;
   }

   // 构建当前的 ServiceId 和 Group  
   NamingService namingService = namingService();
   String serviceId = registration.getServiceId();
   String group = nacosDiscoveryProperties.getGroup();

   // 包括 IP , 端口 , 分配名 , 元数据  
   Instance instance = getNacosInstanceFromRegistration(registration);

   try {
      // 注册当前实例 , 同时会添加心跳
      // serverProxy.registerService(groupedServiceName, groupName, instance);
      namingService.registerInstance(serviceId, group, instance);
   }
   catch (Exception e) {
      log.error("nacos registry, {} register failed...{},", serviceId,
            registration.toString(), e);
      // rethrow a RuntimeException if the registration is failed.
      // issue : https://github.com/alibaba/spring-cloud-alibaba/issues/1132
      rethrowRuntimeException(e);
   }
}

四. 服务的发现

之前在Nacos 基础 中 , 我们大概看过一点 , 这里无非是再看一下

Nacos 的不同版本获取的方式是有很大区别的 , 这里主要针对 spring-cloud-starter-alibaba-nacos-discovery : 2.2.5 版本来看一下 .

4.1 Nacos Server 的发现

Step 1 : 发起的起点

private List<NacosServer> getServers() {
      String group = discoveryProperties.getGroup();
      List<Instance> instances = discoveryProperties.namingServiceInstance()
            .selectInstances(serviceId, group, true);
      return instancesToServerList(instances);
}

Step 2 : 发现的主流程

到这里就和之前的逻辑串起来了 , 后面就是反向调用 Nacos Server 中提供的接口即可

// C- NacosNamingService
public List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy,
        boolean subscribe) throws NacosException {
    ServiceInfo serviceInfo = hostReactor
                .getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName),
                        StringUtils.join(clusters, ","));
    return selectInstances(serviceInfo, healthy);
}

这里获取中会从 Map<String, ServiceInfo> serviceInfoMap 中获取数据 , 下面来看一下 ServiceInfo 是如何获取的

public void updateServiceNow(String serviceName, String clusters) {
    ServiceInfo oldService = getServiceInfo0(serviceName, clusters);
    try {
        // 同样通过代理类发起调用
        String result = serverProxy.queryList(serviceName, clusters, pushReceiver.getUdpPort(), false);
        
        if (StringUtils.isNotEmpty(result)) {
            processServiceJson(result);
        }
    } catch (Exception e) {
        NAMING_LOGGER.error("[NA] failed to update serviceName: " + serviceName, e);
    } finally {
        if (oldService != null) {
            synchronized (oldService) {
                oldService.notifyAll();
            }
        }
    }
}


// 发起远程调用
public String queryList(String serviceName, String clusters, int udpPort, boolean healthyOnly)
        throws NacosException {
    
    final Map<String, String> params = new HashMap<String, String>(8);
    params.put(CommonParams.NAMESPACE_ID, namespaceId);
    params.put(CommonParams.SERVICE_NAME, serviceName);
    params.put("clusters", clusters);
    params.put("udpPort", String.valueOf(udpPort));
    params.put("clientIP", NetUtils.localIP());
    params.put("healthyOnly", String.valueOf(healthyOnly));
    
    // 这里可以看到具体的 API : 
    return reqApi(UtilAndComs.nacosUrlBase + "/instance/list", params, HttpMethod.GET);
}

总结

这篇文章有点简单 , 了解的不深 , 但是应该是很有用 , 出现问题在核心的地方打个断点 ,能节省很多时间

TODO : 流程图今天就不画了 , 后面有时间补一个