In-depth Microservice-Nacos Registration Instance Source Code Analysis

Get into the habit of writing together! This is the 11th day of my participation in the "Nuggets Daily New Plan · April Update Challenge", click to view the details of the event .

Microservices Series Article Directory


foreword

This series takes you deep into the basic use and underlying principles of each framework of the microservice Spring system. The last article took you to learn how to compile and run the Nacos source code. This article will explain the source code analysis of the Nacos registration instance in detail.


Nacos registration instance source code analysis

Client source code tracking

Nacos Registration Discovery API.pngRegister an instance, the client will encapsulate an instance information to initiate a request to the Nacos server and register

1. Start the configuration class from sping.factories

Start by registering spring-cloud-starter-alibaba-nacos-discovery's sping.factories with the client service

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. Find the core configuration class 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. Update registered instance information

Update the temporary registered instance to the ephemeralInstances attribute of the cluster. The service discovery finds the temporary instance and finally finds this attribute from the memory. According to the copyOnWrite idea, create a copy table for comparison, and then assign the value 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;
    }
}
复制代码

Guess you like

Origin juejin.im/post/7086636231483719717