Nacos-服务注册

一.介绍

1.1 Nacos的实现原理

   

   

   

   

   

   

   

   

   

图片来自: https://www.cnblogs.com/wuzhenzhao/p/13625491.html

1.2 本地启动

  1. 下载源码包: https://github.com/alibaba/nacos.git

下载好后,我个人选择切换到master分支,代码比较完整。

  1. 找到console,选择Nacos这个类,然后启动, 如果有报错,可能是本地config文件夹下需要一个application.properties文件,从console里面复制一份到本地即可;
  2. 本地启动时候设置一下单机模式: -Dnacos.standalone=true

1.3 注册中心

注册中心其实就是一个Springboot的项目

@SpringBootApplication(scanBasePackages = "com.alibaba.nacos")

@ServletComponentScan

@EnableScheduling

public class Nacos {

  public static void main(String[] args) {

    SpringApplication.run(Nacos.class, args);

  }

}

启动后会初始化naming,console,config包下面的controllers接口,保存到methodsCache中。

@Component

@EnableScheduling

@PropertySource("/application.properties")

public class ConsoleConfig {

    @Autowired

    private ControllerMethodsCache methodsCache;

    @PostConstruct

    public void init() {

         methodsCache.initClassMethod("com.alibaba.nacos.naming.controllers");

        methodsCache.initClassMethod("com.alibaba.nacos.console.controller");

        methodsCache.initClassMethod("com.alibaba.nacos.config.server.controller");

    }

}

1.4 Client启动类

spring-cloud-starter-alibaba-nacos-discovery 工程中 META-INF\spring.factories文件注入的类;

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

com.alibaba.cloud.nacos.NacosDiscoveryAutoConfiguration,\

com.alibaba.cloud.nacos.ribbon.RibbonNacosAutoConfiguration,\

com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpointAutoConfiguration,\

com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientAutoConfiguration,\

com.alibaba.cloud.nacos.discovery.configclient.NacosConfigServerAutoConfiguration

org.springframework.cloud.bootstrap.BootstrapConfiguration=\

com.alibaba.cloud.nacos.discovery.configclient.NacosDiscoveryClientConfigServiceBootstrapConfiguration

二. 服务注册客户端的处理

先来一张服务注册流程图:

图片来自: https://blog.csdn.net/wangwei19871103/article/details/105787403

2.1 NacosDiscoveryAutoConfiguration配置实例化

public class NacosDiscoveryAutoConfiguration {

    @Bean

    public NacosServiceRegistry nacosServiceRegistry(

           NacosDiscoveryProperties nacosDiscoveryProperties) {

           return new NacosServiceRegistry(nacosDiscoveryProperties);

    }

    @Bean

    @ConditionalOnBean(AutoServiceRegistrationProperties.class)

    public NacosRegistration nacosRegistration(

           NacosDiscoveryProperties nacosDiscoveryProperties,

           ApplicationContext context) {

        return new NacosRegistration(nacosDiscoveryProperties, context);

      }

    @Bean

    @ConditionalOnBean(AutoServiceRegistrationProperties.class)

    public NacosAutoServiceRegistration nacosAutoServiceRegistration(

           NacosServiceRegistry registry, //上面方法实例化的

           AutoServiceRegistrationProperties autoServiceRegistrationProperties,

           NacosRegistration registration) { //上面方法实例化的

        return new NacosAutoServiceRegistration(registry,  autoServiceRegistrationProperties, registration);

    }

}

2.1.1 实例化 NacosDiscoveryProperties

读取的是配置文件里面的内容:

@ConfigurationProperties("spring.cloud.nacos.discovery")

public class NacosDiscoveryProperties { ... )

2.1.2 实例化 NacosServiceRegistry

public NacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {

       this.nacosDiscoveryProperties = nacosDiscoveryProperties;

       this.namingService = nacosDiscoveryProperties.namingServiceInstance();

}

后面工厂调用到:

  namingService= NacosFactory.createNamingService(getNacosProperties());

  getNacosProperties方法里面加一些秘钥,clusterName啥的操作;

  创建方法里面用到反射;

2.1.3 实例化 NacosRegistration

public NacosRegistration(NacosDiscoveryProperties nacosDiscoveryProperties,

ApplicationContext context) {

  this.nacosDiscoveryProperties = nacosDiscoveryProperties;

    this.context = context;

}

这里就填充一下; 不过在init初始化方法里面填充了一部分management和心跳的信息;

2.1.4 实例化 NacosAutoServiceRegistration

参数 AutoServiceRegistrationProperties里面是几个配置;

public NacosAutoServiceRegistration(ServiceRegistry<Registration> serviceRegistry,

      AutoServiceRegistrationProperties autoServiceRegistrationProperties,

       NacosRegistration registration) {

        super(serviceRegistry, autoServiceRegistrationProperties);

        this.registration = registration;

}  

2.2 NacosDiscoveryClientAutoConfiguration

@Configuration

@ConditionalOnNacosDiscoveryEnabled

@AutoConfigureBefore({ SimpleDiscoveryClientAutoConfiguration.class,

CommonsClientAutoConfiguration.class })

public class NacosDiscoveryClientAutoConfiguration {

    @Bean //读取配置文件实例化

    @ConditionalOnMissingBean

    public NacosDiscoveryProperties nacosProperties() {

         return new NacosDiscoveryProperties();

    }

    @Bean

    public DiscoveryClient nacosDiscoveryClient(

        NacosDiscoveryProperties discoveryProperties) {

    return new NacosDiscoveryClient(discoveryProperties); // 当前的实例的客户端

    }

    @Bean

    @ConditionalOnMissingBean

    @ConditionalOnProperty(value = "spring.cloud.nacos.discovery.watch.enabled", matchIfMissing = true)

    public NacosWatch nacosWatch(NacosDiscoveryProperties nacosDiscoveryProperties) {

      return new NacosWatch(nacosDiscoveryProperties); // 该类会在启动后发布一次心跳

    }

 }

2.2.1 实例化NacosWatch

public NacosWatch(NacosDiscoveryProperties properties) {

    this(properties, getTaskScheduler());

}

@Override

public void start() {

    if (this.running.compareAndSet(false, true)) {

         this.watchFuture = this.taskScheduler.scheduleWithFixedDelay(

         this::nacosServicesWatch, this.properties.getWatchDelay()); //延迟30000ms

    }

}

public void nacosServicesWatch() {

    // nacos doesn't support watch now , publish an event every 30 seconds.

    this.publisher.publishEvent(

         new HeartbeatEvent(this, nacosWatchIndex.getAndIncrement()));

}

2.3 NacosNamingService

1.4.第2节中创建的NamingService跟服务注册、心跳等有关的所有代码都在该类中,在该类构造方法中会调用init方法;

2.3.1 init()方法

private void init(Properties properties) {

    namespace = InitUtils.initNamespaceForNaming(properties); //获取namespace

    initServerAddr(properties); //初始化注册中心地址

        InitUtils.initWebRootContext();

    initCacheDir(); // 初始化缓存目录地址,默认在/nacos/naming/public 目录下

        initLogName(properties); // 初始化日志名称,默认为naming.log

   

    eventDispatcher = new EventDispatcher(); // 后台会启动一个线程用来创建namingEvent

    //下面几个重要, BeatReactor创建心跳反应堆,HostReactor创建主机反应堆

    serverProxy = new NamingProxy(namespace, endpoint, serverList);

        serverProxy.setProperties(properties);

        beatReactor = new BeatReactor(serverProxy, initClientBeatThreadCount(properties));

        hostReactor = new HostReactor(eventDispatcher, serverProxy, cacheDir, isLoadCacheAtStart(properties), initPollingThreadCount(properties));

}

2.3.2 EventDispatcher方法创建namingEvent

public EventDispatcher() {

  executor = Executors.newSingleThreadExecutor(new ThreadFactory() {

  executor.execute(new Notifier());

}

private class Notifier implements Runnable {

    @Override

    public void run() {

         while (true) {

         ServiceInfo serviceInfo = null; //5

         serviceInfo = changedServices.poll(5, TimeUnit.MINUTES);

      List<EventListener> listeners = observerMap.get(serviceInfo.getKey());

      for (EventListener listener : listeners) {

             List<Instance> hosts = Collections.unmodifiableList(serviceInfo.getHosts());

        listener.onEvent(new NamingEvent(serviceInfo.getName(), serviceInfo.getGroupName(), serviceInfo.getClusters(), hosts));

           }

      }

    }

}

2.3.3 InitUtils.initNamespaceForNaming()方法

nacos的数据模型是由三元组确定,分别为Namespace、Group、dataid。下面方法用于获取Namespace,默认返回public

/* 初始化命名空间以进行命名。 配置初始化不一样,因此不能直接重用 */

public static String initNamespaceForNaming(Properties properties) {

  String tmpNamespace = null;

        String isUseCloudNamespaceParsing =

    properties.getProperty(PropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING,

       System.getProperty(SystemPropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING,

       String.valueOf(Constants.DEFAULT_USE_CLOUD_NAMESPACE_PARSING)));

  if (Boolean.valueOf(isUseCloudNamespaceParsing)) {

    tmpNamespace = TenantUtil.getUserTenantForAns();

         tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace,  new    Callable<String>() {

        @Override

         public String call() {

           String namespace = System.getProperty(SystemPropertyKeyConst.ANS_NAMESPACE);

           return namespace;

         }

       });

       tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new     Callable<String>() {

         @Override

         public String call() {

           String namespace = System.getenv(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_NAMESPACE);

        return namespace;

      }

    });

  }

     tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {

      @Override

       public String call() {

         String namespace = System.getProperty(PropertyKeyConst.NAMESPACE);

    return namespace;

    }

  });

  if (StringUtils.isEmpty(tmpNamespace) && properties != null) {

    tmpNamespace = properties.getProperty(PropertyKeyConst.NAMESPACE);

  }

  tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {

    @Override

    public String call() {

      return UtilAndComs.DEFAULT_NAMESPACE_ID; //默认返"public"

    }

  });

  return tmpNamespace;

}

2.3.4 NamingProxy方法

public NamingProxy(String namespaceId, String endpoint, String serverList) {

      this.namespaceId = namespaceId;

       this.endpoint = endpoint;

       if (StringUtils.isNotEmpty(serverList)) {

         this.serverList = Arrays.asList(serverList.split(","));

         if (this.serverList.size() == 1) {

           this.nacosDomain = serverList;

         }

       }

       initRefreshSrvIfNeed();

}

内部函数 initRefreshSrvIfNeed

private void initRefreshSrvIfNeed() {

  // endpoint为空则返回     

  endpointScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {

        @Override

        public Thread newThread(Runnable r) {

           Thread t = new Thread(r);

          t.setName("com.alibaba.nacos.client.naming.serverlist.updater");

           t.setDaemon(true);

          return t;

        }

  });

  //启动了定时线程更新服务注册时间; 30

    executorService.scheduleWithFixedDelay(new Runnable() {

       @Override

      public void run() {

        refreshSrvIfNeed();

    }

  }, 0, vipSrvRefInterMillis, TimeUnit.MILLISECONDS);

       refreshSrvIfNeed();

}

2.4 NacosAutoServiceRegistration

2.4.1 NacosWatch添加发布者

2.4.2 Springboot启动时调用 finishRefresh

@Override

protected void finishRefresh() {

      super.finishRefresh();

      WebServer webServer = startWebServer();

      if (webServer != null) {

         publishEvent(new ServletWebServerInitializedEvent(webServer, this));

      }

}

2.4.3 NacosAutoServiceRegistration绑定上面的event

public class NacosAutoServiceRegistration

  extends AbstractAutoServiceRegistration<Registration> { ...}

  父类中方法

  @Override

  @SuppressWarnings("deprecation")

  public void onApplicationEvent(WebServerInitializedEvent event) {

        bind(event);

  }

@Deprecated

public void bind(WebServerInitializedEvent event) {

  his.start();

}

继续调用start:

public void start() {

  if (!isEnabled()) {

    return;

  }

  if (!this.running.get()) {

    this.context.publishEvent(

    new InstancePreRegisteredEvent(this, getRegistration())); //发布实例预注册事件

    register(); //这里调用注册方法

    if (shouldRegisterManagement()) {

      registerManagement();

    }

    this.context.publishEvent(

      new InstanceRegisteredEvent<>(this, getConfiguration())); //发布实例注册事件

    this.running.compareAndSet(false, true);

  }

}

2.5 NacosServiceRegistry

2.5.1 NacosServiceRegistry注册

@Override

public void register(Registration registration) {

        //对应当前应用的application.name

       String serviceId = registration.getServiceId();

  //表示服务实例信息

       Instance instance = getNacosInstanceFromRegistration(registration);

           //通过命名服务进行注册

      namingService.registerInstance(serviceId, instance);

}

2.5.2 NacosNamingService开始注册实例

主要做两个动作

  1. 如果当前注册的是临时节点,则构建心跳信息,通过beat反应堆来构建心跳任务
  2. 调用registerService发起服务注册

@Override

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

  //是临时节点,则构建心跳信息

     if (instance.isEphemeral()) {

       BeatInfo beatInfo = new BeatInfo();

       beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));

       beatInfo.setIp(instance.getIp());

       beatInfo.setPort(instance.getPort());

       beatInfo.setCluster(instance.getClusterName());

       beatInfo.setWeight(instance.getWeight());

       beatInfo.setMetadata(instance.getMetadata());

       beatInfo.setScheduled(false);

       long instanceInterval = instance.getInstanceHeartBeatInterval();

       beatInfo.setPeriod(instanceInterval == 0 ? DEFAULT_HEART_BEAT_INTERVAL : instanceInterval);

          //启动心跳定时任务, 添加心跳信息进行处理

       beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);

  }

     //调用服务代理类进行注册

     serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);

}

addBeatInfo方法里面有一个调度任务

class BeatTask implements Runnable {

     BeatInfo beatInfo;

    public BeatTask(BeatInfo beatInfo) {

           this.beatInfo = beatInfo;

     }

     @Override

    public void run() {

       long result = serverProxy.sendBeat(beatInfo);

       long nextTime = result > 0 ? result : beatInfo.getPeriod();

       //在给定延时之后调度任务

       executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);

}

   

调用registerService方法(逻辑比较简单):

public void registerService(String serviceName, String groupName, Instance instance) {

       final Map<String, String> params = new HashMap<String, String>(9);

       //params 填充参数,namespaceId,serviceName,groupName,ip.port,ephemeral等参数(...省略)

    //请求API

       reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST);

}

调用reqAPI方法:服务在进行注册的时候会轮询配置好的注册中心的地址:

reqApi()首先会发送一次注册请求,如果请求失败则进入重试注册循环中,有3次重试次数,如果依然注册失败则会抛出异常。

public String reqAPI(String api, Map<String, String> params, List<String> servers, String method) {

       params.put(CommonParams.NAMESPACE_ID, getNamespaceId());

       Exception exception = new Exception();

       if (servers != null && !servers.isEmpty()) {

         //随机获取一台服务器节点

         Random random = new Random(System.currentTimeMillis());

        int index = random.nextInt(servers.size());

         // 遍历服务列表

        for (int i = 0; i < servers.size(); i++) {

      String server = servers.get(index); //获得索引位置的服务节点

          try {

             return callServer(api, params, server, method); //调用指定服务

           } catch (NacosException e) { ... }

             index = (index + 1) % servers.size(); //轮询下一个

               }

       }

    for (int i = 0; i < UtilAndComs.REQUEST_DOMAIN_RETRY_COUNT; i++) {

  return callServer(api, params, nacosDomain);

     }

}

callServer(api, params, server, method) 调用,通过HttpURLConnection 进行发起调用。

public String callServer(String api, Map<String, String> params, String curServer, String method) {

  HttpClient.HttpResult result = HttpClient.request(url, headers, params, UtilAndComs.ENCODING, method)

}

debug会发现有一个请求: http://xxxx:8848/nacos/v1/ns/instance ;

另外还有: /nacos/v1/ns/instance/beat的定时任务请求;

三. Nacos注册中心端的处理

3.1 InstanceController.register()接口

@CanDistro

@PostMapping

@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)

public String register(HttpServletRequest request) throws Exception {

  final Instance instance = parseInstance(request); //从请求中解析出instance实例

  //调用 ServiceManager 进行服务的注册

       serviceManager.registerInstance(namespaceId, serviceName, instance);

       return "ok";

}

3.1.1 registerInstance方法

serviceManager,通过该类管理service生命周期活动;

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

      //创建空服务,实际上是初始化一个ConcurrentHashMap集合

      createEmptyService(namespaceId, serviceName, instance.isEphemeral());

      //serviceMap中,根据namespaceIdserviceName得到一个服务对象

      Service service = getService(namespaceId, serviceName);

  //调用addInstance创建一个服务实例, 添加一致性协议

      addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);

}

3.1.2 createServiceIfAbsent方法

public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster){

    Service service = getService(namespaceId, serviceName); //serviceMap中获取服务对象

     if (service == null) { //如果为空。则初始化

           service = new Service();

           (...省略部分代码)

           service.setLastModifiedMillis(System.currentTimeMillis());

           service.recalculateChecksum();

          if (cluster != null) {

             cluster.setService(service);

             service.getClusterMap().put(cluster.getName(), cluster);

           }

           service.validate();

          putServiceAndInit(service);

          if (!local) {

             addOrReplaceService(service);

          }

  }

}

Nacos是通过不同的 namespace 来维护服务的,而每个namespace下有不同的group,不同的group下才有对应的Service ,再通过这个 serviceName 来确定服务实例。   

第一次进来则会进入初始化,初始化完会调用 putServiceAndInit;

/** Map(namespace, Map(group::serviceName, Service)). */

private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();

   

putServiceAndInit方法

private void putServiceAndInit(Service service) throws NacosException {

       putService(service); //把服务信息保存到serviceMap集合

       service.init(); //建立心跳检测机制

  //实现数据一致性监听,ephemeral=true表示采用Distro协议,false表示采用raft

       consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service);

    consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service);

}

3.1.3 addInstance方法

获取到服务后把服务实例添加到集合中,然后基于一致性协议进行数据的同步。然后调用addInstance ;

public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips){

  //组装key,例如: com.alibaba.nacos.naming.iplist.ephemeral.public##DEFAULT_GROUP@@nacos-config-client

    String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);

  Service service = getService(namespaceId, serviceName); //获取刚刚组装的服务

  synchronized (service) {

  List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);

  Instances instances = new Instances();

  instances.setInstanceList(instanceList);

          consistencyService.put(key, instances); // 上一步实现监听的类里添加注册服务

     }

}

然后给服务注册方发送注册成功的响应(return "ok")。结束服务注册流程;

3.2 InstanceController.beat()接口

@CanDistro

@PutMapping("/beat")

@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)

public ObjectNode beat(HttpServletRequest request) throws Exception {

      ObjectNode result = JacksonUtils.createEmptyJsonNode();

  result.put(SwitchEntry.CLIENT_BEAT_INTERVAL, switchDomain.getClientBeatInterval());

      String beat = WebUtils.optional(request, "beat", StringUtils.EMPTY); //解析beat参数

      RsInfo clientBeat = null;

  if (StringUtils.isNotBlank(beat)) {

        clientBeat = JacksonUtils.toObj(beat, RsInfo.class);

  }

      //获取namespaceId,serviceName ,serviceName ,ip ,port等参数(...省略)

      //获取实例

       Instance instance = serviceManager.getInstance(namespaceId, serviceName, serviceName, ip, port);

  //如果没有获取到实例则新建一个

  if (instance == null) {

  instance = new Instance();

  //填充实例的属性值ip ,port(...省略)

  //注册实例

  serviceManager.registerInstance(namespaceId, serviceName, instance);

       }

  Service service = serviceManager.getService(namespaceId, serviceName);

  if (clientBeat == null) {

  clientBeat = new RsInfo();

  clientBeat.setIp(ip);

  clientBeat.setPort(port);

  clientBeat.setCluster(clusterName);

  }

  //处理客户端心跳方法下章单独分析,主要是客户端续约

      service.processClientBeat(clientBeat);

  result.put(CommonParams.CODE, NamingResponseCode.OK);

    if (instance.containsMetadata(PreservedMetadataKeys.HEART_BEAT_INTERVAL)) {

        result.put(SwitchEntry.CLIENT_BEAT_INTERVAL, instance.getInstanceHeartBeatInterval());

  }

  result.put(SwitchEntry.LIGHT_BEAT_ENABLED, switchDomain.isLightBeatEnabled());

  return result; //返回客户端心跳

}

   

   

   

参考:

https://blog.csdn.net/V_zxw/article/details/108244306,

https://www.cnblogs.com/wuzhenzhao/p/13625491.html

posted @ 2021-01-01 13:39  将军上座  阅读(1332)  评论(0编辑  收藏  举报