深入微服务-Nacos 注册实例源码解析

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

微服务系列文章目录


前言

本系列带着大家深入微服务 Spring体系的各个框架的基本使用以及底层原理。上篇文章带着大家学习了Nacos 源码编译运行,本篇文章将详细讲解Nacos 注册实例的源码解析


Nacos 注册实例源码解析

客户端源码追踪

Nacos 注册发现API.png 注册实例,客户端会封装一个实例信息向Nacos服务端发起请求并注册

1、从sping.factories 入手配置类

从客户端服务注册 spring-cloud-starter-alibaba-nacos-discovery的sping.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

复制代码

2、找到核心配置类NacosDiscoveryAutoConfiguration

向下走分表是绑定,发布事件 com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration com.alibaba.cloud.nacos.registry.NacosAutoServiceRegistration org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration#bind org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration#start

public void start() {
		if (!isEnabled()) {
			if (logger.isDebugEnabled()) {
				logger.debug("Discovery Lifecycle disabled. Not starting");
			}
			return;
		}

		// only initialize if nonSecurePort is greater than 0 and it isn't already running
		// because of containerPortInitializer below
		if (!this.running.get()) {
		    // 发布InstancePreRegisteredEvent事件
			this.context.publishEvent(
					new InstancePreRegisteredEvent(this, getRegistration()));
			// 注册逻辑
			register();
			if (shouldRegisterManagement()) {
				registerManagement();
			}
			// 发布InstanceRegisteredEvent事件
			this.context.publishEvent(
					new InstanceRegisteredEvent<>(this, getConfiguration()));
			this.running.compareAndSet(false, true);
		}

	}
复制代码

3、客户端进行服务注册逻辑

com.alibaba.cloud.nacos.registry.NacosAutoServiceRegistration#register

@Override
protected void register() {
   if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
      log.debug("Registration disabled.");
      return;
   }
   if (this.registration.getPort() < 0) {
      this.registration.setPort(getPort().get());
   }
   // 调用父类AbstractAutoServiceRegistration
   super.register();
}
复制代码

org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration#register com.alibaba.cloud.nacos.registry.NacosServiceRegistry#register

	@Override
	public void register(Registration registration) {

		if (StringUtils.isEmpty(registration.getServiceId())) {
			log.warn("No service to register for nacos client...");
			return;
		}

		NamingService namingService = namingService();
		// 获取serviceId 
		String serviceId = registration.getServiceId();
		// 获取服务发现配置的分组
		String group = nacosDiscoveryProperties.getGroup();
        // 封装实例对象,分别从registration和nacosDiscoveryProperties配置获取
		Instance instance = getNacosInstanceFromRegistration(registration);

		try {
		    //注册实例
			namingService.registerInstance(serviceId, group, instance);
			log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
					instance.getIp(), instance.getPort());
		}
		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);
		}
	}
复制代码

注册实例 代码 com.alibaba.nacos.client.naming.NacosNamingService#registerInstance(java.lang.String, com.alibaba.nacos.api.naming.pojo.Instance)

 @Override
    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
        NamingUtils.checkInstanceIsLegal(instance);
        String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
        //判断是否为临时实例
        if (instance.isEphemeral()) {
            //  封装心跳信息
            BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
             // 开启心跳续约
            beatReactor.addBeatInfo(groupedServiceName, beatInfo);
        }
         // serverProxy注册
        serverProxy.registerService(groupedServiceName, groupName, instance);
    }

复制代码

4、封装向服务端发起http请求,进行服务注册

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});
        Map<String, String> params = new HashMap(9);
        params.put("namespaceId", this.namespaceId);
        params.put("serviceName", serviceName);
        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", JSON.toJSONString(instance.getMetadata()));
        this.reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, (String)"POST");
    }
复制代码

服务端源码解析

1、客户端调用服务端注册接口

com.alibaba.nacos.naming.controllers.InstanceController#register

@CanDistro
@PostMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String register(HttpServletRequest request) throws Exception {
    
    final String namespaceId = WebUtils
            .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
    final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
    NamingUtils.checkServiceNameFormat(serviceName);
    
    final Instance instance = parseInstance(request);
    // 实现注册逻辑
    serviceManager.registerInstance(namespaceId, serviceName, instance);
    return "ok";
}
复制代码

com.alibaba.nacos.naming.core.ServiceManager#registerInstance

public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
    // 初始化Service Map 和初始化定时任务
    // Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>()
    createEmptyService(namespaceId, serviceName, instance.isEphemeral());
    // 根据namespaceId 和 serviceName从serviceMap中获取Service 
    Service service = getService(namespaceId, serviceName);
    
    if (service == null) {
        throw new NacosException(NacosException.INVALID_PARAM,
                "service not found, namespace: " + namespaceId + ", service: " + serviceName);
    }
    
    addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}
复制代码

2、添加实例

addInstance方法它会获取service以及instanceList

com.alibaba.nacos.naming.core.ServiceManager#addInstance

public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
            throws NacosException {
        
        String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
        // 1.获取Service
        Service service = getService(namespaceId, serviceName);
        
        synchronized (service) {
           // 获取instanceList 
            List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);
            
            Instances instances = new Instances();
            instances.setInstanceList(instanceList);
            
            consistencyService.put(key, instances);
        }
    }
复制代码

com.alibaba.nacos.naming.consistency.DelegateConsistencyServiceImpl#put

3、将实例信息更新到内存注册表以及同步实例信息到集群其他节点

@Override
    public void put(String key, Record value) throws NacosException {
        // 将注册实例更新到内存注册表
        onPut(key, value);
        // 同步实例信息到nacos Server 集群其他节点
        distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,
                globalConfig.getTaskDispatchPeriod() / 2);
    }
复制代码

com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl#put

 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);
        }
        
        if (!listeners.containsKey(key)) {
            return;
        }
        
        notifier.addTask(key, DataOperation.CHANGE);
    }
复制代码

4、往阻塞队列task里面放入注册实例数据

com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl.Notifier#addTask

public void addTask(String datumKey, DataOperation action) {
    
    ...
    // private BlockingQueue<Pair<String, DataOperation>> tasks = new ArrayBlockingQueue<>(1024 * 1024)
    tasks.offer(Pair.with(datumKey, action));
}
复制代码

5、初始化Notifier 任务,从队列拉取数据进行处理

5、DistroConsistencyServiceImpl init()方法会初始化一个Notifier 任务,循环从阻塞队列拉取数据进行处理 com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl#init

 @PostConstruct
    public void init() {
        GlobalExecutor.submitDistroNotifyTask(notifier);
    }
复制代码

com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl.Notifier#run

@Override
public void run() {
    Loggers.DISTRO.info("distro notifier started");
    // 自旋
    for (; ; ) {
        try {
            Pair<String, DataOperation> pair = tasks.take();
            // 处理逻辑
            handle(pair);
        } catch (Throwable e) {
            Loggers.DISTRO.error("[NACOS-DISTRO] Error while handling notifying task", e);
        }
    }
}
复制代码

com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl.Notifier#handle

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++;
            
            try {
                // 判断是什么类型
                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 notifying listener of key: {}", datumKey, e);
            }
        }
        
        ...
    } catch (Throwable e) {
        Loggers.DISTRO.error("[NACOS-DISTRO] Error while handling notifying task", e);
    }
}
复制代码

com.alibaba.nacos.naming.core.Service#onChange

@Override
public void onChange(String key, Instances value) throws Exception {
    
    Loggers.SRV_LOG.info("[NACOS-RAFT] datum is changed, key: {}, value: {}", key, value);
    
    ...
    
    updateIPs(value.getInstanceList(), KeyBuilder.matchEphemeralInstanceListKey(key));
    
    recalculateChecksum();
}
复制代码

com.alibaba.nacos.naming.core.Service#updateIPs

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());
    getPushService().serviceChanged(this);
    StringBuilder stringBuilder = new StringBuilder();
    
    for (Instance instance : allIPs()) {
        stringBuilder.append(instance.toIpAddr()).append("_").append(instance.isHealthy()).append(",");
    }
    
    Loggers.EVT_LOG.info("[IP-UPDATED] namespace: {}, service: {}, ips: {}", getNamespaceId(), getName(),
            stringBuilder.toString());
    
}
复制代码

6、更新注册实例信息

将临时的注册实例更新到了cluster的ephemeralInstances 属性上去,服务发现查找临时实例最终从内存里找到的就是这个属性 根据copyOnWrite思想进行比对,创建复制表,进行对比,然后赋值 com.alibaba.nacos.naming.core.Cluster#updateIps

public void updateIps(List<Instance> ips, boolean ephemeral) {
    
    Set<Instance> toUpdateInstances = ephemeral ? ephemeralInstances : persistentInstances;
    // 创建赋值表
    HashMap<String, Instance> oldIpMap = new HashMap<>(toUpdateInstances.size());
    
    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());
            
            // do not update the ip validation status of updated ips
            // because the checker has the most precise result
            // Only when ip is not marked, don't we update the health status of IP:
            if (!ip.isMarked()) {
                ip.setHealthy(oldIP.isHealthy());
            }
            
            if (ip.isHealthy() != oldIP.isHealthy()) {
                // ip validation status updated
                Loggers.EVT_LOG.info("{} {SYNC} IP-{} {}:{}@{}", getService().getName(),
                        (ip.isHealthy() ? "ENABLED" : "DISABLED"), ip.getIp(), ip.getPort(), getName());
            }
            
            if (ip.getWeight() != oldIP.getWeight()) {
                // ip validation status updated
                Loggers.EVT_LOG.info("{} {SYNC} {IP-UPDATED} {}->{}", getService().getName(), oldIP.toString(),
                        ip.toString());
            }
        }
    }
    
    List<Instance> newIPs = subtract(ips, oldIpMap.values());
    if (newIPs.size() > 0) {
        Loggers.EVT_LOG
                .info("{} {SYNC} {IP-NEW} cluster: {}, new ips size: {}, content: {}", getService().getName(),
                        getName(), newIPs.size(), newIPs.toString());
        
        for (Instance ip : newIPs) {
            HealthCheckStatus.reset(ip);
        }
    }
    
    List<Instance> deadIPs = subtract(oldIpMap.values(), ips);
    
    if (deadIPs.size() > 0) {
        Loggers.EVT_LOG
                .info("{} {SYNC} {IP-DEAD} cluster: {}, dead ips size: {}, content: {}", getService().getName(),
                        getName(), deadIPs.size(), deadIPs.toString());
        
        for (Instance ip : deadIPs) {
            HealthCheckStatus.remv(ip);
        }
    
    toUpdateInstances = new HashSet<>(ips);
    // 判断是否是临时实例
    if (ephemeral) {
        ephemeralInstances = toUpdateInstances;
    } else {
        persistentInstances = toUpdateInstances;
    }
}
复制代码

猜你喜欢

转载自juejin.im/post/7086636231483719717