Nacos 服务注册与发现之源码解密

什么是Nacos

Naocs 是Alibaba开源的微服务生态中的一个组件,可以实现微服务治理中的服务注册发现,配置中心功能。

项目地址: https://github.com/alibaba/Nacos

Service is a first-class citizen in Nacos. Nacos supports discovering, configuring, and managing almost all types of services:

Kubernetes Service

gRPC & Dubbo RPC Service

Spring Cloud RESTful Service

引用官方的话来说,在Naocs中服务的地位非常重要。所以Nacos支持服务的发现、配置和管理所有类型的服务,包括Kubernetes Service、gRPC 和 Dubbo RPC Service 以及 Spring Cloud RESTful Service。

  • 简单来说Naocs就是一个帮助开发者集中管理服务的一个基于Spring Boot 项目的中间件而已。它可以帮助开发者完成服务的 注册和发现以及对服务的配置的管理。
  • Nacos在微服务整体架构中充当注册中心和配置中心(相当于Eureka 和Spring Cloud Config的组合)

Nacos 和 Eureka 的区别

  • (在微服务整体架构中扮演的角色不同)Nacos 不仅仅是服务注册中心还是配置中心,Eureka 只是充当服务注册中心
  • Nacos 满足CPA 理论中的CP , Eureka 满足的是AP
  • (Server集群间的数据同步方式不同) Nacos Server 集群中各个实例实现数据同步采用Leader-Follower机制,Eureka Server集群采用实例间的相互注册。
  • (客户端获取服务信息的方式不同) Nacos 中采用了在客户端每隔10秒主动轮询Server集群的方式并且还实现了通过基于UDP 方式的Server端主动推送数据到客户端, Eureka中采用定时任务,每隔30秒中去拉去一次数据。

Nacos 服务注册与发现的流程

Nacos 注册中心流程图

Nacos 服务注册与发现之源码解密_第1张图片

Nacos 配置中心流程图

Nacos 服务注册与发现之源码解密_第2张图片

集群选举

我们已经搭建起来了Nacos的集群。

只要涉及到集群,那么就会涉及到主从的关系。在Nacos中采用了的是Leader-Follower模式。显然这种模式是存在者选举机制的,谁当老大谁当老二,还是要走走民主的过程滴。

选举算法 Raft (Raft算法的Nacos实现的源码后面在补充)

Raft算法演示

Nacos 集群采用Raft算法,来实现的Leader选举。

选举算法在RaftCore中,包括数据处理和同步。

Raft中,节点有三种角色:

  • Leader:负责接收来自客户端的请求 (所以的事务请求都走他这里)
  • Candidate:在选举的过程中产生的一种临时角色
  • Follower:负责响应Leader或Candidate的请求

选举过程在两个时间节点发送:

  • Nacos Server 服务启动的时候
  • Leader节点挂掉了的时候

选举的过程:

​ 所有节点在最开始的时候,都是Follower状态。如果在一段时间内没有收到Leader的心跳(可能是Leader挂了或者根本没有Leader),

就会将自己的状态改为Candidate,开始竞选,并且会增加一个叫term的票据。

  • follower会先投自己一票,并且给其他节点发送Vote,等待其他节点回复。
  • 约束条件在任一个term中,单个节点最能只能投一票
  • 选举成功后,Leader和Followr们会维持一个心跳连接。
  • 在这个过程中可能会,出现下面集中情况:
    • 收到了过半的票数,那么这个节点就成为了Leader
    • 收到了其他节点发给自己的她已经成为了Leader,那么自己变为Follower
    • 一段时间内没有收到投票,继续增加term然后重新发起选举。

数据同步

在上面的演示地址的动画中,我们可以清晰的看到。Raft算法实现数据同步的方式是采用了 2PC提交的方式保证了数据的一致性。

过程:

  • Leader 收到了来自客户端的事务请求(如果是客户端直接请求到了Follower节点,Follower节点也会将这个事务性请求转发给Leader节点),修改自己的值。
  • Leader修改过后,会发送给他得Follower们,Follower们收到Leader发送过来的数据,会先将这个数据写入他们的本地日志中不提交,然后返回给Leader一个ACK信息,表示他们已经收到了Leader传递过来的数据。
  • 如果Leader 收到Follower的ACK信息数量,超过了一半,直接返回给客户端修改成功,通知Follower提交数据。反之通知客户端修改失败,并且通知Follower删除日志中的数据。

结合源码分析Nacos的实现

源码分析 服务的注册于发现

服务注册 :

在程序启动过的时候,我们可以看到这个类 NacosDiscoveryProperties,有一个注解@ConfigurationProperties 去加载我们在application.properties 文件中的 spring.cloud.nacos.discovery 的相关配置

//加载配置类
@ConfigurationProperties("spring.cloud.nacos.discovery")
public class  {
     
	private String serverAddr;
    ...
    @PostConstruct
	public void init() throws SocketException {
     
       	...
		// 获取服务的Ip地址
		if (StringUtils.isEmpty(ip)) {
     
			// traversing network interfaces if didn't specify a interface
			if (StringUtils.isEmpty(networkInterface)) {
     
				ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
			}
			else {
     
            	...
            }	
		}
		//设置参数的默认值
		this.overrideFromEnv(environment);
    }

}

真正的服务注册类 NacosServiceRegistry

NacosServiceRegistry,在Spring 初始化加载 NacosServiceRegistryAutoConfigugration配置类的时候,会被创建

NacosServiceRegistryAutoConfiguretion

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled",
		matchIfMissing = true)
@AutoConfigureAfter({
      AutoServiceRegistrationConfiguration.class,
		AutoServiceRegistrationAutoConfiguration.class,
		NacosDiscoveryAutoConfiguration.class })
public class NacosServiceRegistryAutoConfiguration {
     

	@Bean
	public NacosServiceRegistry nacosServiceRegistry(
			NacosDiscoveryProperties nacosDiscoveryProperties) {
     
        //开始创建NacosServiceRegistry
        //nacosDiscoveryProperties在上面已经被初始化完成
		return new NacosServiceRegistry(nacosDiscoveryProperties);
	}

这是服务注册时的最主要类 算是源码入口了!!!!

NacosServiceRegistry

//实现了ServiceRegistry这个接口。这个接口是SpringCloud的关于服务注册提供的同一的接口,
public class NacosServiceRegistry implements ServiceRegistry<Registration> {
     
    
	private final NacosDiscoveryProperties nacosDiscoveryProperties;

	private final NamingService namingService;

	public NacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {
     
		this.nacosDiscoveryProperties = nacosDiscoveryProperties;
		this.namingService = nacosDiscoveryProperties.namingServiceInstance();
	}
	//服务注册 
    //很多同学可能会犹豫,这个方法到底是在哪里调用的呢?因为我们在这里并没有看到有那个方法传入了Registration这个参数
    //其实它是在一个名为 AbstractAutoServiceRegistration 这个类里面的 start()方法中调用的。
    //AbstractAutoServiceRegistration 实现了ApplicationListener接口 传入的泛型是WebServerInitializedEvent
    //监听web容器启动的时候执行 里面的bind()方法,在bind()中执行start()方法
	@Override
	public void register(Registration registration) {
     

		if (StringUtils.isEmpty(registration.getServiceId())) {
     
			log.warn("No service to register for nacos client...");
			return;
		}
		
		String serviceId = registration.getServiceId();
		String group = nacosDiscoveryProperties.getGroup();
		//使用配置文件,去创建一个Inctanse实例,包括服务提供者的Ip地址和端口号
		Instance instance = getNacosInstanceFromRegistration(registration);

		try {
     
            //注册一个实例到nacos server,这个resgisterInstance方法在 NacosNamingService类中
			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);
		}
	}

NacosNamingSevice

public class NacosNamingService implements NamingService {
     
    ...
   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);
            beatInfo.setPeriod(instance.getInstanceHeartBeatInterval());
            //这里面会启动一个线程认为BeatTask,默认每5秒获取一次心跳数据
            this.beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
        }
		//同过NamingProxy代理类去真正的拼装请求参数,调用NacosAPI。
        this.serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
    }
}

NamingProxy

public class NamingProxy {
     
    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});
        //可以看到Alibaba的这个Nacos开源组件就是很接地气 哈哈哈O(∩_∩)O 这个代码太熟悉了把
        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()));
        //拼装完参数以后,调用Nacos Rest Api
        this.reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, "POST");
    }
    
    ...
      //其他的reqApi重载方法我们可以不去看,但是这个方法需要去看看
      public String reqAPI(String api, Map<String, String> params, String body, List<String> servers, String method) throws NacosException {
     
        params.put("namespaceId", this.getNamespaceId());
        if (CollectionUtils.isEmpty(servers) && StringUtils.isEmpty(this.nacosDomain)) {
     
            throw new NacosException(400, "no server available");
        } else {
     
            NacosException exception = new NacosException();
            if (servers != null && !servers.isEmpty()) {
     
                Random random = new Random(System.currentTimeMillis());
                //生成一个0到ServerAddrList.size()长度的随机整数
                //这里其实就实在模拟客户端的负载均很算法,
                int index = random.nextInt(servers.size());
                int i = 0;
                while(i < servers.size()) {
     
                    //随机选择出一个nacos server
                    String server = (String)servers.get(index);
                    try {
     
                        //调用API 注册服务
                        return this.callServer(api, params, body, server, method);
                    } catch (NacosException var13) {
     
                        exception = var13;
                        if (LogUtils.NAMING_LOGGER.isDebugEnabled()) {
     
                            LogUtils.NAMING_LOGGER.debug("request {} failed.", server, var13);
                        }

                        index = (index + 1) % servers.size();
                        ++i;
                    }
                }
            }
			...
        }
    }
   	...
    //reqAPI的方法,经过一系列的重载后,最终执行了这个callServer方法
    public String callServer(String api, Map<String, String> params, String body, String curServer, String method) throws NacosException {
     
        long start = System.currentTimeMillis();
        long end = 0L;
        this.injectSecurityInfo(params);
        List<String> headers = this.builderHeaders();
        String url;
        if (!curServer.startsWith("https://") && !curServer.startsWith("http://")) {
     
            if (!curServer.contains(":")) {
     
                curServer = curServer + ":" + this.serverPort;
            }

            url = HttpClient.getPrefix() + curServer + api;
        } else {
     
            url = curServer + api;
        }
		//没错把使用的就是 HttpClient客户端请求
        HttpResult result = HttpClient.request(url, headers, params, body, "UTF-8", method);
        end = System.currentTimeMillis();
        //开启数据监控
        MetricsMonitor.getNamingRequestMonitor(method, url, String.valueOf(result.code)).observe((double)(end - start));
        if (200 == result.code) {
     
            return result.content;
        } else if (304 == result.code) {
     
            return "";
        } else {
     
            throw new NacosException(result.code, result.content);
        }
    }
}

至此Provider向Nacos中注册服务的流程分析的就差不多了。简单的总结一下这个服务注册的过程:

  • 在Spring容器启动的时候,先通过了NacosDiscoveryProperties获取了application.properties中的关于spring.cloud.nacos.discovery的配置数据
  • 然后加载到NacosServiceRegistryAutoConfiguretion的时候,创建一个NacosServiceRegistry,这个NacosServiceRegistry类是真正的服务注册的入口。
  • NacosServiceRegistry中有一个register()方法,这个方法将会在Web容器启动的时候被执行。在构造方法中创建了NamingSerivce,这个NamingSerivce相当于是我们对操作nacos server的client
  • 在register()方法中调用了NamingService的registerInstance()方法,去注册实例。这个registerInstance()方法中,完成了创建一个线程池去执行心跳检测的操作。(默认每隔5s执行一次心跳检查),然后调用NamingProxy的registerService()注册服务
  • 在NamingProxy中的registerService()方法中拼装了参数,通过一个重载的reqAPI()方法,完成了客户端负载均衡的操作。生成一个0~serverAddrList.size()的随机数字。
  • 调用Nacos API完成服务注册
服务发现:

首先在 NacosDiscoveryAutoConfiguration 自动配置的类,在初始化NacosServiceDiscovery

NacosDiscoveryAutoConfiguretion:

public class NacosDiscoveryAutoConfiguration {
     
	//这里是老规矩,从application.properties 初始化配置数据
	@Bean
	@ConditionalOnMissingBean
	public NacosDiscoveryProperties nacosProperties() {
     
		return new NacosDiscoveryProperties();
	}

	@Bean
	@ConditionalOnMissingBean
	public NacosServiceDiscovery nacosServiceDiscovery(
			NacosDiscoveryProperties discoveryProperties) {
     
        //初始化Nacos服务发现类 这个类中封装了获取服务List的方法
		return new NacosServiceDiscovery(discoveryProperties);
	}
}

NacosDiscoveryClientConfiguration:

@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@ConditionalOnBlockingDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
@AutoConfigureBefore({
      SimpleDiscoveryClientAutoConfiguration.class,
		CommonsClientAutoConfiguration.class })
//NacosDiscoveryAutoConfiguration 初始化装载完成以后,在会初始化这个Bean,确保了依赖关系正确
@AutoConfigureAfter(NacosDiscoveryAutoConfiguration.class)
public class NacosDiscoveryClientConfiguration {
     

	@Bean
	public DiscoveryClient nacosDiscoveryClient(
			NacosServiceDiscovery nacosServiceDiscovery) {
     
        //初始化NacosDiscoveryClient客户端
		return new NacosDiscoveryClient(nacosServiceDiscovery);
	}

	@Bean
	@ConditionalOnMissingBean
	@ConditionalOnProperty(value = "spring.cloud.nacos.discovery.watch.enabled",
			matchIfMissing = true)
	public NacosWatch nacosWatch(NacosDiscoveryProperties nacosDiscoveryProperties,
			ObjectProvider<TaskScheduler> taskScheduler) {
     
		return new NacosWatch(nacosDiscoveryProperties, taskScheduler);
	}

}

NcosDiscoveryClient:这个类算是我们的代码入口了,请看

//实现了Spring cloud 提供的DiscoveryClient提供的接口
public class NacosDiscoveryClient implements DiscoveryClient {
     

	private NacosServiceDiscovery serviceDiscovery;

	public NacosDiscoveryClient(NacosServiceDiscovery nacosServiceDiscovery) {
     
		this.serviceDiscovery = nacosServiceDiscovery;
	}

	...
    //获取服务实例 传入的serviceId
	@Override
	public List<ServiceInstance> getInstances(String serviceId) {
     
		try {
     
			return serviceDiscovery.getInstances(serviceId);
		}
		catch (Exception e) {
     
			throw new RuntimeException(
					"Can not get hosts from nacos server. serviceId: " + serviceId, e);
		}
	}
	...
}

NacosServiceDiscovery

public class NacosServiceDiscovery {
     
    ...
    /**
	 * Return all instances for the given service.
	 * @param serviceId id of service
	 * @return list of instances
	 * @throws NacosException nacosException
	 */
	public List<ServiceInstance> getInstances(String serviceId) throws NacosException {
     
		String group = discoveryProperties.getGroup();
        //可以看到还是在使用namingService调用方法,和注册的时候一样。
		List<Instance> instances = discoveryProperties.namingServiceInstance()
				.selectInstances(serviceId, group, true);
        //数据转化并返回
		return hostToServiceInstanceList(instances, serviceId);
	}
    ...
}

NacosNamingService 这个类我们需要关注两个地方,下面也就之列出这两个地方的代码以作参考

public class NacosNamingService implements NamingService {
     
    ...
        //初始化代码
      private void init(Properties properties) {
     
        namespace = InitUtils.initNamespaceForNaming(properties);
        initServerAddr(properties);
        InitUtils.initWebRootContext();
        initCacheDir();
        initLogName(properties);

        eventDispatcher = new EventDispatcher();
        serverProxy = new NamingProxy(namespace, endpoint, serverList, properties);
        //创建一个服务消费者和Nacos Server的心跳监听
        beatReactor = new BeatReactor(serverProxy, initClientBeatThreadCount(properties));
        //创建服务动态更线程类
        hostReactor = new HostReactor(eventDispatcher, serverProxy, cacheDir, isLoadCacheAtStart(properties),
            initPollingThreadCount(properties));
    }
   	...
        // 获取Server的服务实例
        @Override
    public List<Instance> getAllInstances(String serviceName, String groupName, List<String> clusters, boolean subscribe) throws NacosException {
     

        ServiceInfo serviceInfo;
        //判断是否订阅了
        //如果订阅了通过udp的方式同送服务变更信息
        if (subscribe) {
     
            serviceInfo = hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));
        } else {
     
            serviceInfo = hostReactor.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));
        }
        List<Instance> list;
        if (serviceInfo == null || CollectionUtils.isEmpty(list = serviceInfo.getHosts())) {
     
            return new ArrayList<Instance>();
        }
        return list;
    }
		...

    
}

HostReactor 服务动态更新类

我们知道服务动态更新有两种

  • pull请求主动拉去更新的服务数据

    public class HostReactor {
           
        ...
       	  public ServiceInfo getServiceInfo(final String serviceName, final String clusters) {
           
    
            NAMING_LOGGER.debug("failover-mode: " + failoverReactor.isFailoverSwitch());
            String key = ServiceInfo.getKey(serviceName, clusters);
            if (failoverReactor.isFailoverSwitch()) {
           
                return failoverReactor.getService(key);
            }
    
            ServiceInfo serviceObj = getServiceInfo0(serviceName, clusters);
    
            if (null == serviceObj) {
           
                serviceObj = new ServiceInfo(serviceName, clusters);
    
                serviceInfoMap.put(serviceObj.getKey(), serviceObj);
    
                updatingMap.put(serviceName, new Object());
                updateServiceNow(serviceName, clusters);
                updatingMap.remove(serviceName);
    
            } else if (updatingMap.containsKey(serviceName)) {
           
    
                if (UPDATE_HOLD_INTERVAL > 0) {
           
                    // hold a moment waiting for update finish
                    synchronized (serviceObj) {
           
                        try {
           
                            serviceObj.wait(UPDATE_HOLD_INTERVAL);
                        } catch (InterruptedException e) {
           
                            NAMING_LOGGER.error("[getServiceInfo] serviceName:" + serviceName + ", clusters:" + clusters, e);
                        }
                    }
                }
            }
    		//开启一个定时任务,这个任务会在默认1s后开始执行。具体执行UpdateTask任务
            scheduleUpdateIfAbsent(serviceName, clusters);
    
            return serviceInfoMap.get(serviceObj.getKey());
        }
         public void scheduleUpdateIfAbsent(String serviceName, String clusters) {
           
            if (futureMap.get(ServiceInfo.getKey(serviceName, clusters)) != null) {
           
                return;
            }
    
            synchronized (futureMap) {
           
                if (futureMap.get(ServiceInfo.getKey(serviceName, clusters)) != null) {
           
                    return;
                }
    			//执行UpdateTask任务
                ScheduledFuture<?> future = addTask(new UpdateTask(serviceName, clusters));
                futureMap.put(ServiceInfo.getKey(serviceName, clusters), future);
            }
        }
    
    }
    

    UpdateTask

      public class UpdateTask implements Runnable {
           
            @Override
            public void run() {
           
                try {
           
                    //查询本地缓存
                    ServiceInfo serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));
    				//如果本地缓存为空,立即发起更新
                    if (serviceObj == null) {
           
                        updateServiceNow(serviceName, clusters);
                        //并开启定时任务
                        executor.schedule(this, DEFAULT_DELAY, TimeUnit.MILLISECONDS);
                        return;
                    }
    				//判断服务是否已经国企
                    if (serviceObj.getLastRefTime() <= lastRefTime) {
           
                        updateServiceNow(serviceName, clusters);
                        serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));
                    } else {
           
                        // if serviceName already updated by push, we should not override it
                        // since the push data may be different from pull through force push
                        refreshOnly(serviceName, clusters);
                    }
    				//更新最后一次刷新时间
                    lastRefTime = serviceObj.getLastRefTime();
    
                    if (!eventDispatcher.isSubscribed(serviceName, clusters) &&
                        !futureMap.containsKey(ServiceInfo.getKey(serviceName, clusters))) {
           
                        // abort the update task:
                        NAMING_LOGGER.info("update task is stopped, service:" + serviceName + ", clusters:" + clusters);
                        return;
                    }
    				//延迟10s后执行
                    executor.schedule(this, serviceObj.getCacheMillis(), TimeUnit.MILLISECONDS);
    
    
                } catch (Throwable e) {
           
                    NAMING_LOGGER.warn("[NA] failed to update serviceName: " + serviceName, e);
                }
    
            }
        }
    
  • push请求同送数据

    HostReactor

    在HostReactor的构造方法中初始化了一个 pushReceiver 用作udp 的接收个发送

     public HostReactor(EventDispatcher eventDispatcher, NamingProxy serverProxy, String cacheDir,
                           boolean loadCacheAtStart, int pollingThreadCount) {
           
    
            executor = new ScheduledThreadPoolExecutor(pollingThreadCount, new ThreadFactory() {
           
                @Override
                public Thread newThread(Runnable r) {
           
                    Thread thread = new Thread(r);
                    thread.setDaemon(true);
                    thread.setName("com.alibaba.nacos.client.naming.updater");
                    return thread;
                }
            });
    
            this.eventDispatcher = eventDispatcher;
            this.serverProxy = serverProxy;
            this.cacheDir = cacheDir;
            if (loadCacheAtStart) {
           
                this.serviceInfoMap = new ConcurrentHashMap<String, ServiceInfo>(DiskCache.read(this.cacheDir));
            } else {
           
                this.serviceInfoMap = new ConcurrentHashMap<String, ServiceInfo>(16);
            }
    
            this.updatingMap = new ConcurrentHashMap<String, Object>();
            this.failoverReactor = new FailoverReactor(this, cacheDir);
            this.pushReceiver = new PushReceiver(this);
        }
    

    PushReceiver

     public void run() {
           
         //不断循环监听server端的push请求,然后调用processServiceJSON方法对数据信息解析,并返回
            while (true) {
           
                try {
           
                    // byte[] is initialized with 0 full filled by default
                    byte[] buffer = new byte[UDP_MSS];
                    DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
    				//接收服务端的push过来的数据
                    udpSocket.receive(packet);
    
                    String json = new String(IoUtils.tryDecompress(packet.getData()), "UTF-8").trim();
                    NAMING_LOGGER.info("received push data: " + json + " from " + packet.getAddress().toString());
    
                    PushPacket pushPacket = JSON.parseObject(json, PushPacket.class);
                    String ack;
                    if ("dom".equals(pushPacket.type) || "service".equals(pushPacket.type)) {
           
                        hostReactor.processServiceJSON(pushPacket.data);
    
                        // send ack to server
                        ack = "{\"type\": \"push-ack\""
                            + ", \"lastRefTime\":\"" + pushPacket.lastRefTime
                            + "\", \"data\":" + "\"\"}";
                    } else if ("dump".equals(pushPacket.type)) {
           
                        // dump data to server
                        ack = "{\"type\": \"dump-ack\""
                            + ", \"lastRefTime\": \"" + pushPacket.lastRefTime
                            + "\", \"data\":" + "\""
                            + StringUtils.escapeJavaScript(JSON.toJSONString(hostReactor.getServiceInfoMap()))
                            + "\"}";
                    } else {
           
                        // do nothing send ack only
                        ack = "{\"type\": \"unknown-ack\""
                            + ", \"lastRefTime\":\"" + pushPacket.lastRefTime
                            + "\", \"data\":" + "\"\"}";
                    }
    
                    udpSocket.send(new DatagramPacket(ack.getBytes(Charset.forName("UTF-8")),
                        ack.getBytes(Charset.forName("UTF-8")).length, packet.getSocketAddress()));
                } catch (Exception e) {
           
                    NAMING_LOGGER.error("[NA] error while receiving push data", e);
                }
            }
        }
    
    

上面就是Nacos服务发现的基本过程,现在总结一下:

  • 根据流程图我们知道,服务发现的流程有两种:client端的主动poll和server端的push操作(基于UDP)
  • 在应用启动的时候,还是会按照老规矩先去加载配置数据到 NacosDiscovryProperties类中,然后通过NacosDiscovryAutoConfiguretionNacosDiscovryClientConfiguretion 初始化 NacosDiscovryClient 客户端
  • NacosDiscoveyClient 通过getInstances()方法调用了 NacosDiscovery的getInstances()方法
  • 在NaocsDiscovry 的getInstances()方法中,创建一个NacosNamingService 并且调用了 NacosNamingService的getInstances()方法
  • 在NacosNamingService的构造方法中,又开启了服务心跳监听线程类(BeatReactor)和服务动更新线程类(HostReactor)
  • 在NacosNamingService 的getInstances()方法中,会执行HostReactor的getServiceInfo()方法去获取server的服务
  • 在HostReatcor的getServiceInfo()方法中启动了一个线程池去执行UpdateTask任务去poll更新服务。在UpdateTask的run方法中先查询本地缓存是否有改服务,如果没有那么就会立即去请求。然后判断过期时间这些之类的。
  • 同时会在HostReactor的构造方法中创建了一个PushReceiver创建一个UDP Socket和Server端建立连接。
  • 在PushReceiver 的构造方法中又会启动一个线程池,在这个类的run方法中会不断的循环去监听server端push的数据,然后将数据解析为ServiceInfo 返回。

你可能感兴趣的:(Spring,Cloud,Alibaba)