Nacos源码之注册原理

源码阅读前的建议:
1.了解过Spring&SpringBoot源码(必须)。

源码分析Nacos的版本为:1.4

注册流程:

  • 1.客户端发起注册
  • 2.服务端完成注册

1. 前序

项目中使用Spring Boot与Nacos整合的包为

   <dependency>
       <groupId>com.alibaba.cloud</groupId>
       <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
   </dependency>

然后在配置文件中进行配置一下:

server:
  port: 8085
spring:
  application:
    name: xxx
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        username: nacos
        password: nacos

指定一下Nacos地址,用户名&密码,启动项目后即可注册到Nacos中,总的来说使用起来是比较简单的,导包—》配置–》启动,就结束了。
看到这里的同学可能会有疑问,这个过程究竟发生了什么事情,Ncaos中注册的服务实例究竟是什么东西,那就让我们先从启动流程分析开始!

1.启动流程分析

熟悉Spring Boot的同学可能都会知道,Spring Boot的老套路,用什么三方框架只要引入整合的starter即可,所以我们源码分析的源头也在于上面引入的starter。
在这里插入图片描述我们找到starter,找到下面的spring.factories文件,这个是Spring Boot启动时会去读取的自动配置文件,EnableAutoConfiguration指向的是会去加载的自动配置类,其中NacosServiceRegistryAutoConfiguration是我们要重点分析的类。

2.NacosServiceRegistryAutoConfiguration

简要分析一下重要的类:

@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
		AutoServiceRegistrationAutoConfiguration.class,
		// 对配置文件中的配置进行了映射加载 NacosDiscoveryProperties
		NacosDiscoveryAutoConfiguration.class })
public class NacosServiceRegistryAutoConfiguration {

	@Bean
	public NacosServiceRegistry nacosServiceRegistry(
			NacosDiscoveryProperties nacosDiscoveryProperties) {
		   ....
	}

	@Bean
	@ConditionalOnBean(AutoServiceRegistrationProperties.class)
	public NacosRegistration nacosRegistration(
			ObjectProvider<List<NacosRegistrationCustomizer>> registrationCustomizers,
			....
	}
	
	/**
	* NacosAutoServiceRegistration 重要的类,把上面创建的Bean都进行了引用
	**/
	@Bean
	@ConditionalOnBean(AutoServiceRegistrationProperties.class)
	public NacosAutoServiceRegistration nacosAutoServiceRegistration(
			NacosServiceRegistry registry,
			AutoServiceRegistrationProperties autoServiceRegistrationProperties,
			NacosRegistration registration) {
		return new NacosAutoServiceRegistration(registry,
				autoServiceRegistrationProperties, registration);
	}
}

2.1 NacosAutoServiceRegistration

分析类继承图
在这里插入图片描述图中可看出,NacosAutoServiceRegistration 实现了 ApplicationListener接口,这说明了该类是一个事件监听类,我们直接找到事件触发时会调用的onApplicationEvent()方法:

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

最终在父类AbstractAutoServiceRegistration中发现了实现的该方法,并且监听的事件是WebServerInitializedEvent事件,那么该事件是何时触发的呢,这个地方就是关联了Spring源码了:
在这里插入图片描述
finishRefresh()是Spring容器启动完成的最后的一个方法,也就是说,Spring容器启动完成后,通过发布的事件就会走上面的事件监听者方法,走bind()逻辑。

	public void bind(WebServerInitializedEvent event) {
		ApplicationContext context = event.getApplicationContext();
		this.start();
	}
	
	public void start() {
		if (!this.running.get()) {
		  // 发布一个注册前的事件
			this.context.publishEvent(
					new InstancePreRegisteredEvent(this, getRegistration()));
			// 客户端注册的方法		
			register();
			// 注册完成后的事件
			this.context.publishEvent(
					new InstanceRegisteredEvent<>(this, getConfiguration()));
			this.running.compareAndSet(false, true);
		}
	}
	protected void register() {
	  // ServiceRegistry serviceRegistry 对应nacos的实现是:NacosServiceRegistry
		this.serviceRegistry.register(getRegistration());
	}
	
  /**
  * NacosServiceRegistry:register()
  * Registration存放的是要注册的信息
  **/
	public void register(Registration registration) {
		NamingService namingService = namingService();
		String serviceId = registration.getServiceId();
		String group = nacosDiscoveryProperties.getGroup();
    // 实例类,真正要注册的内容
		Instance instance = getNacosInstanceFromRegistration(registration);
		// 注册实例到Nacos远程Server端
		namingService.registerInstance(serviceId, group, instance);
		log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
			instance.getIp(), instance.getPort());
	}

Instance 类:

@JsonInclude(Include.NON_NULL)
public class Instance implements Serializable {
    private String instanceId;
    private String ip;
    private int port;
    private double weight = 1.0D;
    private boolean healthy = true;
    private boolean enabled = true;
    private boolean ephemeral = true;
    private String clusterName;
    private String serviceName;
}   

NacosNamingService.registerInstance:

   @Override
    public void registerInstance(String serviceName, String groupName, Instance instance) {
        NamingUtils.checkInstanceIsLegal(instance);
        String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
         // 没有显示设置就是为true
        if (instance.isEphemeral()) {
           // 心跳线程启动
            BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
            beatReactor.addBeatInfo(groupedServiceName, beatInfo);
        }
        // 注册
        serverProxy.registerService(groupedServiceName, groupName, instance);
    }

NamingProxy.registerService;

public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
      final Map<String, String> params = new HashMap<String, String>(16);
      params.put(CommonParams.NAMESPACE_ID, namespaceId);
      params.put(CommonParams.SERVICE_NAME, serviceName);
      params.put(CommonParams.GROUP_NAME, groupName);
      params.put(CommonParams.CLUSTER_NAME, 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()));
      // 发送http请求到server端:POST:/nacos/v1/ns/instance
      reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
}

注册总结:

  • 1.注册时机:利用Spring事件机制,解耦注册。
  • 2.所谓的服务实例其实就是本机的IP,服务名称等信息。
  • 3.注册是异步的直接发送Http请求给Server端,后续心跳保活。

以上是客户端注册部分的源码解析。

1.服务端接受注册

前面我们看到客户端注册的最后会发送一个 POST:/nacos/v1/ns/instance 的请求到Server端,官网上注册实例的路径也是这个,那么我们只要找到这个Controller中对应的接口,沿着这个接口请求进行分析即可。
在这里插入图片描述
下面分析的代码均在 Nacos Server端,需要下载 Nacos 1.4版本的源码,用Git拉下来导入项目即可。
在这里插入图片描述
com.alibaba.nacos.Nacos 是启动类。

com.alibaba.nacos.naming.controllers.InstanceController#register 是注册的接口,也是源码分析的入口:

     /**
     * Register new instance.
     */
    @CanDistro
    @PostMapping
    @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
    public String register(HttpServletRequest request) throws Exception {
    	...
        // 请求解析成 Instance
        final Instance instance = parseInstance(request);
        // 服务实例注册
        serviceManager.registerInstance(namespaceId, serviceName, instance);
        return "ok";
    }

把传过来的参数进行解析,包装成一个 Instance 对象,然后进行注册。

registerInstance(namespaceId, serviceName, instance):

    public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
        // 1.创建空 service
        createEmptyService(namespaceId, serviceName, instance.isEphemeral());
        // 2.注册实例
        addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
    }

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

    /**
     * Nacos注册实例表:Map(namespace, Map(group::serviceName, Service)).
     */
    private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();
    public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
            throws NacosException {
        // 先从注册实例表里面查一下
        Service service = getService(namespaceId, serviceName);
        // 第一次创建 service,肯定是null
        if (service == null) {
            service = new Service();
            service.setName(serviceName);
            service.setNamespaceId(namespaceId);
            service.setGroupName(NamingUtils.getGroupName(serviceName));
            // now validate the service. if failed, exception will be thrown
            service.setLastModifiedMillis(System.currentTimeMillis());
            service.recalculateChecksum();
            // 1.1 添加到map & 初始化service
            putServiceAndInit(service);
        }
    }
    private void putServiceAndInit(Service service) throws NacosException {
        // 添加到 实例注册表
        putService(service);
        // 1.1.1执行service的初始化方法
        service.init();
    }
    public void init() {
        // 健康检测 任务启动 5秒
        HealthCheckReactor.scheduleCheck(clientBeatCheckTask);
        for (Map.Entry<String, Cluster> entry : clusterMap.entrySet()) {
            entry.getValue().setService(this);
            entry.getValue().init();
        }
    }

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

    public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
            throws NacosException { 
        // com.alibaba.nacos.naming.iplist.ephemeral.public##DEFAULT_GROUP@@web-consumer    
        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);
        }
    }
    
    public void put(String key, Record value) throws NacosException {
        // 持久 & 临时,默认 临时
        // com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl#put
        mapConsistencyService(key).put(key, value);
    }
    public void put(String key, Record value) throws NacosException {
        // 核心方法 onPut
        onPut(key, value);
        // 集群同步方法
        distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,
                globalConfig.getTaskDispatchPeriod() / 2);
    }

注册流程已经走完,核心在于onPut方法怎么做后续处理的:

private volatile Notifier notifier = new Notifier();

public void onPut(String key, Record value) {
    if (KeyBuilder.matchEphemeralInstanceListKey(key)) {
        Datum<Instances> datum = new Datum<>();
        datum.value = (Instances) value;
        datum.key = key;
        datum.timestamp.incrementAndGet();
        dataStore.put(key, datum);
    }
    // notifier 一个线程类,初始化的时候,线程启动
    notifier.addTask(key, DataOperation.CHANGE);
}

public class Notifier implements Runnable {
        private ConcurrentHashMap<String, String> services = new ConcurrentHashMap<>(10 * 1024);
        /**
         * 存放 注册实例的队列,,进行异步消费
         */
        private BlockingQueue<Pair<String, DataOperation>> tasks = new ArrayBlockingQueue<>(1024 * 1024);
        
        public void addTask(String datumKey, DataOperation action) {
            
            if (services.containsKey(datumKey) && action == DataOperation.CHANGE) {
                return;
            }
            if (action == DataOperation.CHANGE) {
                services.put(datumKey, StringUtils.EMPTY);
            }
            // instance 放到队列中
            tasks.offer(Pair.with(datumKey, action));
        }

        @Override
        public void run() {
            for (; ; ) {
                try {
                    Pair<String, DataOperation> pair = tasks.take();
                    // 异步线程消费
                    handle(pair);
                } catch (Throwable e) {
                    Loggers.DISTRO.error("[NACOS-DISTRO] Error while handling notifying task", e);
                }
            }
        }
        private void handle(Pair<String, DataOperation> pair) {
            try {
                String datumKey = pair.getValue0();
                DataOperation action = pair.getValue1();
                services.remove(datumKey);
                int count = 0;
                if (!listeners.containsKey(datumKey)) {
                    return;
                }
                for (RecordListener listener : listeners.get(datumKey)) {
                    count++;  
                    // 会走这个地方
                    if (action == DataOperation.CHANGE) {
                          // 消费通知
                          listener.onChange(datumKey, dataStore.get(datumKey).value);
                          continue;
                      }
                      if (action == DataOperation.DELETE) {
                          listener.onDelete(datumKey);
                          continue;
                      }
            } catch (Throwable e) {
                Loggers.DISTRO.error("[NACOS-DISTRO] Error while handling notifying task", e);
            }
        }
    }

在这里插入图片描述
com.alibaba.nacos.naming.core.Service#onChange

    public void onChange(String key, Instances value) throws Exception {
        // 注册实例
        updateIPs(value.getInstanceList(), KeyBuilder.matchEphemeralInstanceListKey(key));
        recalculateChecksum();
    }

    public void updateIPs(Collection<Instance> instances, boolean ephemeral) {
        Map<String, List<Instance>> ipMap = new HashMap<>(clusterMap.size());
        for (String clusterName : clusterMap.keySet()) {
            ipMap.put(clusterName, new ArrayList<>());
        }
        for (Map.Entry<String, List<Instance>> entry : ipMap.entrySet()) {
            //make every ip mine
            List<Instance> entryIPs = entry.getValue();
            // 注册更新实例
            clusterMap.get(entry.getKey()).updateIps(entryIPs, ephemeral);
        }
        setLastModifiedMillis(System.currentTimeMillis());
        // udp 事件推送
        getPushService().serviceChanged(this);
    }
    
   // com.alibaba.nacos.naming.core.Cluster#updateIps
   public void updateIps(List<Instance> ips, boolean ephemeral) {
   		 // ephemeral. 默认是true,临时实例
   		 // toUpdateInstances 要更新的实例列表
        Set<Instance> toUpdateInstances = ephemeral ? ephemeralInstances : persistentInstances;
        // copy on writer 写时复制,提高并发
        HashMap<String, Instance> oldIpMap = new HashMap<>(toUpdateInstances.size());
        // oldIpMap 旧的实例
        for (Instance ip : toUpdateInstances) {
            oldIpMap.put(ip.getDatumKey(), ip);
        }
        // 更新实例列表
        List<Instance> updatedIPs = updatedIps(ips, oldIpMap.values());
        if (updatedIPs.size() > 0) {
            for (Instance ip : updatedIPs) {
                Instance oldIP = oldIpMap.get(ip.getDatumKey());
            }
        }
        // 改变后的实例
        toUpdateInstances = new HashSet<>(ips);
        if (ephemeral) {
            ephemeralInstances = toUpdateInstances;
        } else {
            persistentInstances = toUpdateInstances;
        }
    }

注册总结:

  • 注册信息包装成Service对象,注册到实例表里面进行维护。
  • 注册过程是异步的,后台线程处理注册,提高吞吐量。
  • 实例表在更新时,用了copy on writer思想,防止在更改过程中,无法进行读请求。

至此,一次注册的大致流程就全部走完了!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值