Nacos-注册中心原理解析

Nacos-注册中心原理解析

  • 一、注册中心
  • 二、Nacos注册中心原理解析
    • 2.1 NamingService
    • 2.2 NacosNamingService
      • 2.2.1 NamingProxy
      • 2.2.2 BeatReactor
      • 2.2.3 HostReactor
        • 2.2.3.1 FailoverReactor
        • 2.2.3.2 PushReceiver
    • 2.3 服务注册
      • 2.3.1 namingProxy.registerService
      • 2.3.1 服务端InstanceController
        • 2.3.1.1 serviceManager.registerInstance
        • 2.3.1.2 Service 服务对象
        • 2.3.1.3 Cluster 集群对象
    • 2.4 服务发现
      • 2.4.1 hostReactor.getServiceInfo
      • 2.4.2 hostReactor.updateServiceNow
      • 2.4.3 hostReactor.processServiceJSON
      • 2.4.4 hostReactor.scheduleUpdateIfAbsent
      • 2.4.5 UpdateTask(HostReactor的内部类)
      • 2.4.6 hostReactor.getServiceInfoDirectlyFromServer
      • 2.4.6 服务端接受客户端请求返回
    • 2.5 心跳检测机制
      • 2.5.1 客户端发送心跳包(BeatReactor)
      • 2.5.2 服务端心跳检查(ClientBeatCheckTask)
    • 2.6 服务列表变化通知
      • 2.6.1 客户端定时发送更新请求(UpdateTask)
      • 2.6.2 服务信息发现变化时,主动通知
  • 三、总结

文章系列

【一、Nacos-配置中心原理解析】
【二、Nacos-配置中心原理解析】
【三、Nacos注册中心集群数据一致性问题】

一、注册中心

服务注册与发现是微服务架构得以运转的核心功能,它不提供任何业务功能,仅仅用来进行服务的发现和注册,并对服务的健康状态进行监控和管理。

其核心的工作原理:

  • 服务提供者注册信息到注册中心上;
  • 服务消费者通过注册中心获取服务地址列表;
  • 注册中心动态感知服务的上线与下限(心跳);
  • 服务列表变化通知(pull、push);

二、Nacos注册中心原理解析

2.1 NamingService

Nacos为服务注册与发现提供了一个SDK类 NamingService,通过该类,可以实现服务的注册与发现、订阅服务的动态变化、获取服务实例、获取注册中心的健康状态等等。

其中,NamingService中的接口,可以分为以下几类:

  • namingService.registerInstance():注册实例
  • namingService.deregisterInstance():取消注册
  • namingService.getAllInstances(String serviceName):获取服务的所有实例
  • namingService.getServicesOfServer(int pageNo, int pageSize):分页查询,从服务器获取所有服务名称
  • namingService.getSubscribeServices():获取当前客户端所有订阅的服务
  • namingService.selectInstances():根据条件获取服务实例
  • namingService.selectOneHealthyInstance():根据条件选择一个健康的实例
  • namingService.subscribe():订阅服务以接收实例更改事件
  • namingService.unsubscribe():取消订阅
  • namingService.getServerStatus():获取服务器健康状态

创建 NamingService:

NamingService namingService = NacosFactory.createNamingService(properties);

获取 NacosNamingService 中带Properties形参的构造函数,然后反射创建。

public class NamingFactory {

    public static NamingService createNamingService(Properties properties) throws NacosException {
        try {
            Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
            Constructor constructor = driverImplClass.getConstructor(Properties.class);
            NamingService vendorImpl = (NamingService)constructor.newInstance(properties);
            return vendorImpl;
        } catch (Throwable e) {
            throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
        }
    }
}

2.2 NacosNamingService

通过构造函数,创建一个NacosNamingService 对象,代码如下:

public class NacosNamingService implements NamingService {
	public NacosNamingService(Properties properties) {
        init(properties);
    }
    
	private void init(Properties properties) {
		// 初始化namespace
        namespace = InitUtils.initNamespaceForNaming(properties);
        // 初始化服务端地址列表
        initServerAddr(properties);
        // 初始化web root上下文
        InitUtils.initWebRootContext();
        // 初始化缓存目录:用于故障转移,user.home + /nacos/naming/ + namespace
        initCacheDir();
        initLogName(properties);

		// 事件调度程序:while(true)线程,如果服务列表发生变更,则发送NamingEvent到当前服务所有listener
        eventDispatcher = new EventDispatcher();
        
        // 创建服务代理类:用于服务实例注册/注销、心跳发送、服务实例获取、服务实例更新/删除/创建等
		// 所有向服务器发送、获取的动作,都在 NamingProxy 中完成。
        serverProxy = new NamingProxy(namespace, endpoint, serverList);
        serverProxy.setProperties(properties);
        
        // 用于心跳检测:如果向Nacos注册一个临时节点,需要创建一个心跳检测任务
        beatReactor = new BeatReactor(serverProxy, initClientBeatThreadCount(properties));
        
        // 用于获取服务端注册信息,故障转移,定时刷盘备份等
        hostReactor = new HostReactor(eventDispatcher, serverProxy, cacheDir, isLoadCacheAtStart(properties), initPollingThreadCount(properties));
    }
}

2.2.1 NamingProxy

创建服务代理类:用于服务实例注册/注销、心跳发送、服务实例获取、服务实例更新/删除/创建等。

所有向服务器发送、获取的动作,都在 NamingProxy 中完成。

public class 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;
            }
        }
        
		// 初始化端点服务列表,并且每隔30秒刷新一次服务列表
        initRefreshSrvIfNeed();
    }

	// 初始化端点服务列表
	private void initRefreshSrvIfNeed() {
		// 端点为空,直接返回
        if (StringUtils.isEmpty(endpoint)) {
            return;
        }

        ScheduledExecutorService 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;
            }
        });

		// 延时任务:刷新端点服务列表,vipSrvRefInterMillis = 30s
        executorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
				// 刷新服务列表
                refreshSrvIfNeed();
            }
        }, 0, vipSrvRefInterMillis, TimeUnit.MILLISECONDS);
		
		// 刷新端点服务列表
        refreshSrvIfNeed();
    }
	
	// 刷新端点服务列表
	private void refreshSrvIfNeed() {
        try {

            if (!CollectionUtils.isEmpty(serverList)) {
                NAMING_LOGGER.debug("server list provided by user: " + serverList);
                return;
            }

            if (System.currentTimeMillis() - lastSrvRefTime < vipSrvRefInterMillis) {
                return;
            }

			// 从端点获取服务器列表
            List<String> list = getServerListFromEndpoint();

            if (CollectionUtils.isEmpty(list)) {
                throw new Exception("Can not acquire Nacos list");
            }

            if (!CollectionUtils.isEqualCollection(list, serversFromEndpoint)) {
                NAMING_LOGGER.info("[SERVER-LIST] server list is updated: " + list);
            }

            serversFromEndpoint = list;
            lastSrvRefTime = System.currentTimeMillis();
        } catch (Throwable e) {
            NAMING_LOGGER.warn("failed to update server list", e);
        }
    }
	
	// 从端点获取服务器列表 http://endpoint/nacos/serverlist
	public List<String> getServerListFromEndpoint() {

        try {
            String urlString = "http://" + endpoint + "/nacos/serverlist";
            List<String> headers = builderHeaders();

            HttpClient.HttpResult result = HttpClient.httpGet(urlString, headers, null, UtilAndComs.ENCODING);
            if (HttpURLConnection.HTTP_OK != result.code) {
                throw new IOException("Error while requesting: " + urlString + "'. Server returned: "
                    + result.code);
            }

            String content = result.content;
            List<String> list = new ArrayList<String>();
            for (String line : IoUtils.readLines(new StringReader(content))) {
                if (!line.trim().isEmpty()) {
                    list.add(line.trim());
                }
            }

            return list;

        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}

2.2.2 BeatReactor

BeatReactor 主要是用来发送心跳包,服务端根据心跳时间,来判断当前服务上线、下线。

public class BeatReactor {
	public BeatReactor(NamingProxy serverProxy, int threadCount) {
        this.serverProxy = serverProxy;

        executorService = new ScheduledThreadPoolExecutor(threadCount, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setDaemon(true);
                thread.setName("com.alibaba.nacos.naming.beat.sender");
                return thread;
            }
        });
    }

	// 服务注册是,如果是临时节点,需要添加一个心跳信息
    public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
        NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
        dom2Beat.put(buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort()), beatInfo);
        
        // 执行心跳任务,beatInfo.getPeriod():心跳间隔
        executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
        
        MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
    }

	// Beat Task
	class BeatTask implements Runnable {

        BeatInfo beatInfo;

        public BeatTask(BeatInfo beatInfo) {
            this.beatInfo = beatInfo;
        }

        @Override
        public void run() {
            if (beatInfo.isStopped()) {
                return;
            }

			// 调用NamingProxy 中的sendBeat方法,发送心跳包,url=/instance/beat
            long result = serverProxy.sendBeat(beatInfo);
			
			// 下一次发送心跳时间
            long nextTime = result > 0 ? result : beatInfo.getPeriod();
            // 创建一个心跳延时任务
            executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);
        }
    }
}

2.2.3 HostReactor

用于获取服务端注册信息,故障转移,定时刷盘备份等。

public class HostReactor {
	
	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) { // 是否在启动是加载缓存
        	// DiskCache.read(this.cacheDir):从缓存目标中读取所有文件,缓存到serviceInfoMap内存中
            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);
		
		// 基于UDP连接,获取服务端注册信息变更通知,更新本地缓存、内存。
        this.pushReceiver = new PushReceiver(this);
    }
}

2.2.3.1 FailoverReactor

用于在注册中心故障时,客户端获取注册实例信息,一种故障转移安全机制。

public class FailoverReactor {
	
	public FailoverReactor(HostReactor hostReactor, String cacheDir) {
        this.hostReactor = hostReactor;
        // 故障转移目标:user.home + /nacos/naming/ + namespace + /failover
        this.failoverDir = cacheDir + "/failover";
        // 初始化
        this.init();
    }
	
	// 初始化
	public void init() {
		// 故障转移开关,5s后执行,判断 failoverDir + FAILOVER_SWITCH文件是否存在
		// 如果不存在或者文件内容为0,关闭故障转移
		// 如果存在,并且文件内容为1,开启故障转移
		// 故障转移:开启一个FailoverFileReader线程,读取 failoverDir 下所有文件,缓存到内存中
        executorService.scheduleWithFixedDelay(new SwitchRefresher(), 0L, 5000L, TimeUnit.MILLISECONDS);

		// 开启一个刷盘延时任务:用于备份服务信息到故障转移目录下(failoverDir)
		// DAY_PERIOD_MINUTES = 24 * 60:每隔24小时备份一次
		// 刷盘:获取所有serviceInfoMap循环遍历,过滤掉其他文件(参照源码),将serviceInfoMap中文件,刷盘到failoverDir目录下
        executorService.scheduleWithFixedDelay(new DiskFileWriter(), 30, DAY_PERIOD_MINUTES, TimeUnit.MINUTES);

        // backup file on startup if failover directory is empty.
        // 如果在启动时,failover 目录为空,先进行刷盘备份,10s后执行
        executorService.schedule(new Runnable() {
            @Override
            public void run() {
                try {
                    File cacheDir = new File(failoverDir);

                    if (!cacheDir.exists() && !cacheDir.mkdirs()) {
                        throw new IllegalStateException("failed to create cache dir: " + failoverDir);
                    }

                    File[] files = cacheDir.listFiles();
                    if (files == null || files.length <= 0) {
                        new DiskFileWriter().run();
                    }
                } catch (Throwable e) {
                    NAMING_LOGGER.error("[NA] failed to backup file on startup.", e);
                }

            }
        }, 10000L, TimeUnit.MILLISECONDS);
    }
}

2.2.3.2 PushReceiver

基于UDP连接,获取服务端注册信息变更通知,更新本地缓存、内存。

public class PushReceiver implements Runnable {

	public PushReceiver(HostReactor hostReactor) {
        try {
            this.hostReactor = hostReactor;
			
			// 建立一个UDP socket连接
            udpSocket = new DatagramSocket();

            executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r);
                    thread.setDaemon(true);
                    thread.setName("com.alibaba.nacos.naming.push.receiver");
                    return thread;
                }
            });
			
			// 执行当前线程
            executorService.execute(this);
        } catch (Exception e) {
            NAMING_LOGGER.error("[NA] init udp socket failed", e);
        }
    }

	@Override
    public void run() {
        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);

				// 阻塞:服务端注册信息发送变更后,会主动监理一个UDP连接,发送消息
                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);
				
				// 服务端应答ack
                String ack;
                if ("dom".equals(pushPacket.type) || "service".equals(pushPacket.type)) {
                	// 处理服务端注册信息 字符串
                	// 1. 更新注册信息到本地缓存
                	// 2. 更新注册信息到本地文件
                    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\":" + "\"\"}";
                }

				// 发送服务端应答ack
                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);
            }
        }
    }
}

2.3 服务注册

namingService.registerInstance(serviceName, "localhost", 8080);

使用 namingService 中的 registerInstance 方法注册服务实例信息,最终都会调用到 registerInstance(String serviceName, String groupName, Instance instance) 方法。

public class NacosNamingService implements NamingService {
	@Override
    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
		// 判断是否临时节点:临时节点需要发送心跳包,维持服务状态,默认为true
        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();
            // 设置间隔,默认DEFAULT_HEART_BEAT_INTERVAL=5s
            beatInfo.setPeriod(instanceInterval == 0 ? DEFAULT_HEART_BEAT_INTERVAL : instanceInterval);
			
			// 使用2.2.2 BeatReactor 定时发送心跳包
            beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
        }

		// serverProxy,即为NamingProxy:注册服务
        serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
    }
}

2.3.1 namingProxy.registerService

public class NamingProxy {

	// 注册服务
	public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
        NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}",
            namespaceId, serviceName, instance);

        final Map<String, String> params = new HashMap<String, String>(9);
        params.put(CommonParams.NAMESPACE_ID, namespaceId);
        params.put(CommonParams.SERVICE_NAME, serviceName);
        params.put(CommonParams.GROUP_NAME, groupName); // 分组,默认DEFAULT_GROUP
        params.put(CommonParams.CLUSTER_NAME, instance.getClusterName()); // 所属集群名称,默认DEFAULT
        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())); // 是否临时节点,默认true,是
        params.put("metadata", JSON.toJSONString(instance.getMetadata())); // 元数据

		// 发送服务注册请求:/nacos/v1/ns/instance
		// reqAPI:主要是发送对应请求到服务端,如果异常,会遍历整改serverList请求
		// 当所有server都请求失败,并且只有一个server服务时会重试(默认三次)
        reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST);
    }
    
}

2.3.1 服务端InstanceController

服务端收到客户端发送的 /nacos/v1/ns/instance POST请求后,先将服务注册到 ServiceManager 中的 Map> 对象中,然后为其中的 Service 添加实例信息,最后交由Nacos集群一致性服务,保持集群数据的一致性。

Nacos存储服务结构如下:

Nacos-注册中心原理解析_第1张图片

@RestController
@RequestMapping(UtilsAndCommons.NACOS_NAMING_CONTEXT + "/instance")
public class InstanceController {
	
    @CanDistro
    @RequestMapping(value = "", method = RequestMethod.POST)
    public String register(HttpServletRequest request) throws Exception {
    	// 获取服务名称
        String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
    	// 获取namespace
        String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);

		// 注册实例
		// parseInstance(request):解析请求参数,封装为 Instance 对象
        serviceManager.registerInstance(namespaceId, serviceName, parseInstance(request));
        return "ok";
    }
}

2.3.1.1 serviceManager.registerInstance

@Component
@DependsOn("nacosApplicationContext")
public class ServiceManager implements RecordListener<Service> {
    /**
     * Map>
     */
    private Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();
	
	public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
		// 创建一个空的Service
        createEmptyService(namespaceId, serviceName, instance.isEphemeral());
		// 获取Service:Map> serviceMap
        Service service = getService(namespaceId, serviceName);

        if (service == null) {
            throw new NacosException(NacosException.INVALID_PARAM,
                "service not found, namespace: " + namespaceId + ", service: " + serviceName);
        }
		// 给当前Service添加实例
        addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
    }

	// 创建一个空的Service
	public void createEmptyService(String namespaceId, String serviceName, boolean local) throws NacosException {
        createServiceIfAbsent(namespaceId, serviceName, local, null);
    }
	
	// 如果不存在,创建一个Service实例
	public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster) throws NacosException {
		// 从缓存中获取Service:Map> serviceMap
        Service service = getService(namespaceId, serviceName);
		
		// 为空,创建Service实例
        if (service == null) {
            Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName);
            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());
            // 重新计算checksum
            service.recalculateChecksum();
	
            if (cluster != null) {
                cluster.setService(service);
                // 设置集群
                service.getClusterMap().put(cluster.getName(), cluster);
            }
            // 参数校验
            service.validate();

			// 判断是否临时节点,客户端默认为true
            if (local) {
            	// 临时节点:添加并且初始化
                putServiceAndInit(service);
            } else {
            	// 持久化节点:添加或替换服务
                addOrReplaceService(service);
            }
        }
    }

    // 临时节点:添加并且初始化
	private void putServiceAndInit(Service service) throws NacosException {
		// 添加:将service对象添加到 Map> serviceMap 中
        putService(service);
	
		// 初始化service:详解2.3.1.2 Service 服务对象
        service.init();
        
        // 临时节点
        // 一致性服务:用于保证Nacos集群中,Service对象数据一致性问题
        // listen(String key, RecordListener listener)
        // key   ->    com.alibaba.nacos.naming.iplist.ephemeral. + namespaceId + ## + serviceName
        // listener -> 当前Service对象
        // 参照后续详解Nacos集群一致性算法Raft
        consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service);

		// 持久化节点
        // 一致性服务:用于保证Nacos集群中,Service对象数据一致性问题
        // listen(String key, RecordListener listener)
        // key   ->    com.alibaba.nacos.naming.iplist. + namespaceId + ## + serviceName
        // listener -> 当前Service对象
        // 参照后续详解Nacos集群一致性算法Raft
        consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service);
        Loggers.SRV_LOG.info("[NEW-SERVICE] {}", service.toJSON());
    }

	// 添加:将service对象添加到 Map> serviceMap 中
	public void putService(Service service) {
        if (!serviceMap.containsKey(service.getNamespaceId())) {
            synchronized (putServiceLock) {
                if (!serviceMap.containsKey(service.getNamespaceId())) {
                    serviceMap.put(service.getNamespaceId(), new ConcurrentHashMap<>(16));
                }
            }
        }
        serviceMap.get(service.getNamespaceId()).put(service.getName(), service);
    }

    // 持久化节点:添加或替换服务
    public void addOrReplaceService(Service service) throws NacosException {
    	// 将当前持久化节点注册信息转发到Nacos集群中的Leader节点进行处理,Follower只处理非事务请求
    	// put(String key, Record value)
    	// key     ->     com.alibaba.nacos.naming.domains.meta. + namespaceId + ## + serviceName
    	// valuy   ->     当前Service对象
        // 参照后续详解Nacos集群一致性算法Raft
        consistencyService.put(KeyBuilder.buildServiceMetaKey(service.getNamespaceId(), service.getName()), service);
    }

	// 给当前Service添加实例
	public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips) throws NacosException {
		// 实例列表key
		// 临时节点:com.alibaba.nacos.naming.iplist.ephemeral. + namespaceId + ## + serviceName
		// 持久化节点:com.alibaba.nacos.naming.iplist. + namespaceId + ## + serviceName
        String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);

		// 从缓存中获取Service:Map> serviceMap
        Service service = getService(namespaceId, serviceName);

		// 为Service添加实例
        List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);

        Instances instances = new Instances();
        instances.setInstanceList(instanceList);

		// 添加到一致性服务中ConcurrentMap datums
        consistencyService.put(key, instances);
    }
	
	// 为Service添加实例
	public List<Instance> addIpAddresses(Service service, boolean ephemeral, Instance... ips) throws NacosException {
        return updateIpAddresses(service, UtilsAndCommons.UPDATE_INSTANCE_ACTION_ADD, ephemeral, ips);
    }
    
	public List<Instance> updateIpAddresses(Service service, String action, boolean ephemeral, Instance... ips) throws NacosException {
		// 从一致性服务中获取Datum对象:ConcurrentMap datums
		// 实例列表key
		// 临时节点:com.alibaba.nacos.naming.iplist.ephemeral. + namespaceId + ## + serviceName
		// 持久化节点:com.alibaba.nacos.naming.iplist. + namespaceId + ## + serviceName
        Datum datum = consistencyService.get(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), ephemeral));

        Map<String, Instance> oldInstanceMap = new HashMap<>(16);
        // 获取当前Service所有Instance对象
        List<Instance> currentIPs = service.allIPs(ephemeral);
        Map<String, Instance> map = new ConcurrentHashMap<>(currentIPs.size());

        for (Instance instance : currentIPs) {
            map.put(instance.toIPAddr(), instance);
        }
        if (datum != null) {
        	// 获取Old Instance
            oldInstanceMap = setValid(((Instances) datum.value).getInstanceList(), map);
        }

        // use HashMap for deep copy:
        HashMap<String, Instance> instanceMap = new HashMap<>(oldInstanceMap.size());
        instanceMap.putAll(oldInstanceMap);

        for (Instance instance : ips) {
            if (!service.getClusterMap().containsKey(instance.getClusterName())) {
                Cluster cluster = new Cluster(instance.getClusterName(), service);
                // 集群初始化,详解参照2.3.1.3 Cluster集群对象
                cluster.init();
                service.getClusterMap().put(instance.getClusterName(), cluster);
                Loggers.SRV_LOG.warn("cluster: {} not found, ip: {}, will create new cluster with default configuration.",
                    instance.getClusterName(), instance.toJSON());
            }

			// 添加 or 删除
            if (UtilsAndCommons.UPDATE_INSTANCE_ACTION_REMOVE.equals(action)) {
                instanceMap.remove(instance.getDatumKey());
            } else {
                instanceMap.put(instance.getDatumKey(), instance);
            }

        }

        if (instanceMap.size() <= 0 && UtilsAndCommons.UPDATE_INSTANCE_ACTION_ADD.equals(action)) {
            throw new IllegalArgumentException("ip list can not be empty, service: " + service.getName() + ", ip list: "
                + JSON.toJSONString(instanceMap.values()));
        }

        return new ArrayList<>(instanceMap.values());
    }
}

2.3.1.2 Service 服务对象

  • Nacos中存储服务信息对象。
  • 一个namespace下可存在多个服务。
public class Service extends com.alibaba.nacos.api.naming.pojo.Service implements Record, RecordListener<Instances> {

	// 检查当前服务中的实例是否可用:根据客户端心跳请求更新时间判断
    @JSONField(serialize = false)
    private ClientBeatCheckTask clientBeatCheckTask = new ClientBeatCheckTask(this);
    
    // 集群Map:key -> 集群名称
    private Map<String, Cluster> clusterMap = new HashMap<>();

	// 更新实例信息
	public void onChange(String key, Instances value) throws Exception{
		......
	}
	
	// 批量更新
	public void updateIPs(Collection<Instance> instances, boolean ephemeral){
		......
	}

	// 初始化
	public void init() {
		// 开启检查检查任务
        HealthCheckReactor.scheduleCheck(clientBeatCheckTask);
		
        for (Map.Entry<String, Cluster> entry : clusterMap.entrySet()) {
            entry.getValue().setService(this);
            entry.getValue().init();
        }
    }
	
	// 获取所有实例信息
	public List<Instance> allIPs(){
		......
	}
}

2.3.1.3 Cluster 集群对象

  • Nacos存储服务集群信息对象。
  • 一个服务可对应多个集群。
  • 一个集群可存在多个实例。
public class Cluster extends com.alibaba.nacos.api.naming.pojo.Cluster implements Cloneable {
	
    // 健康检查任务
    @JSONField(serialize = false)
    private HealthCheckTask checkTask;

	// 持久化节点存储对象
    @JSONField(serialize = false)
    private Set<Instance> persistentInstances = new HashSet<>();

	// 临时节点存储对象
    @JSONField(serialize = false)
    private Set<Instance> ephemeralInstances = new HashSet<>();

	// 初始化
	public void init() {
        if (inited) {
            return;
        }
        // 健康检查任务
        checkTask = new HealthCheckTask(this);

        // 开启健康检查任务
        HealthCheckReactor.scheduleCheck(checkTask);
        inited = true;
    }
}

2.4 服务发现

List<Instance> allInstances = namingService.getAllInstances("serviceName");

使用 namingService 中的 getAllInstances 方法获取服务实例信息,最终都会调用到 List getAllInstances(String serviceName, String groupName, List clusters, boolean subscribe) 方法。

public class NacosNamingService implements NamingService {

	@Override
    public List<Instance> getAllInstances(String serviceName, String groupName, List<String> clusters, boolean subscribe) throws NacosException {

        ServiceInfo serviceInfo;
        // 是否订阅服务动态变化:默认为true
        if (subscribe) {
        	// 获取、并订阅服务动态变化
            serviceInfo = hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));
        } else {
        	// 该方法是直接通过NamingProxy发送请求到服务器,获取服务信息
        	// url=/nacos/v1/ns/instance/list       GET请求
            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 对象是用于获取服务端注册信息,故障转移,定时刷盘备份等,下面我们具体分析一下其中的方法。
Nacos-注册中心原理解析_第2张图片

2.4.1 hostReactor.getServiceInfo

获取服务信息(本地缓存、故障转移中心、远程服务)。

  1. 先从本地缓存获取。
  2. 本地缓存为空,判断是否开启故障转移,如果开启,则从故障转移中心获取。
  3. 否则,从远程Nacos服务中心获取,本缓存到本地。
public class HostReactor {
	
    public ServiceInfo getServiceInfo(final String serviceName, final String clusters) {
        NAMING_LOGGER.debug("failover-mode: " + failoverReactor.isFailoverSwitch());
        // serviceName + @@ + clusters
        String key = ServiceInfo.getKey(serviceName, clusters);

		// 是否开启故障转移
        if (failoverReactor.isFailoverSwitch()) {
        	// 从故障转移中心获取服务信息
            return failoverReactor.getService(key);
        }

		// 从缓存中获取服务信息:Map serviceInfoMap
        ServiceInfo serviceObj = getServiceInfo0(serviceName, clusters);

        if (null == serviceObj) {
        	// 创建一个空的Service
            serviceObj = new ServiceInfo(serviceName, clusters);

			// 放入serviceInfoMap缓存中
            serviceInfoMap.put(serviceObj.getKey(), serviceObj);

			// 正在更新的serviceName:用于处理高并发,保证同一时刻,尽少线程调用url=/nacos/v1/ns/instance/list       GET请求
			// 保证了Nacos Server端的安全性
            updatingMap.put(serviceName, new Object());
            // 更新当前实例
            updateServiceNow(serviceName, clusters);
            updatingMap.remove(serviceName);

        } else if (updatingMap.containsKey(serviceName)) { // 已经存在更新

            if (UPDATE_HOLD_INTERVAL > 0) { // 5000 > 0  >>>> true
                // hold a moment waiting for update finish
                // 高并发:阻塞其他需要获取的线程,等待唤醒
                synchronized (serviceObj) {
                    try {
                    	// 最多等待 5000ms,即5s
                        serviceObj.wait(UPDATE_HOLD_INTERVAL);
                    } catch (InterruptedException e) {
                        NAMING_LOGGER.error("[getServiceInfo] serviceName:" + serviceName + ", clusters:" + clusters, e);
                    }
                }
            }
        }
		
		// 定时更新任务:添加一个UpdateTask,每隔一段时间,发送请求到服务端,更新本地缓存、磁盘缓存等
        scheduleUpdateIfAbsent(serviceName, clusters);

        return serviceInfoMap.get(serviceObj.getKey());
    }
}

2.4.2 hostReactor.updateServiceNow

立即更新服务信息。

发送 url=/nacos/v1/ns/instance/list GET请求

public class HostReactor {
	public void updateServiceNow(String serviceName, String clusters) {
        ServiceInfo oldService = getServiceInfo0(serviceName, clusters);
        try {
			// 发送 url=/nacos/v1/ns/instance/list       GET请求
            String result = serverProxy.queryList(serviceName, clusters, pushReceiver.getUDPPort(), false);

            if (StringUtils.isNotEmpty(result)) {
            	// 处理返回结果:更新缓存,通知事件监听等
                processServiceJSON(result);
            }
        } catch (Exception e) {
            NAMING_LOGGER.error("[NA] failed to update serviceName: " + serviceName, e);
        } finally {
            if (oldService != null) {
                synchronized (oldService) {
                	// 缓存等待线程
                	// 与hostReactor.getServiceInfo中的阻塞对应
                	//		synchronized (serviceObj) {
                    //			try {
                    					// 最多等待 5000ms,即5s
                    //    			serviceObj.wait(UPDATE_HOLD_INTERVAL);
                    //			} catch (InterruptedException e) {
                    //    			NAMING_LOGGER.error("[getServiceInfo] serviceName:" + serviceName + ", clusters:" + clusters, e);
                    //			}
                    //		}
                    oldService.notifyAll();
                }
            }
        }
    }
}

2.4.3 hostReactor.processServiceJSON

  1. 处理返回结果
  2. 更新本地缓存
  3. 通知事件监听
  4. 更新磁盘缓存
public class HostReactor {
	// 处理返回结果
	public ServiceInfo processServiceJSON(String json) {
        ServiceInfo serviceInfo = JSON.parseObject(json, ServiceInfo.class);
        ServiceInfo oldService = serviceInfoMap.get(serviceInfo.getKey());
        
        // 忽略空或错误推送
        if (serviceInfo.getHosts() == null || !serviceInfo.validate()) {
            //empty or error push, just ignore
            return oldService;
        }

        boolean changed = false;
		
		// 如果oldService不为空,需要进行合并处理
        if (oldService != null) {
			// getLastRefTime():最后更新时间
            if (oldService.getLastRefTime() > serviceInfo.getLastRefTime()) {
                NAMING_LOGGER.warn("out of date data received, old-t: " + oldService.getLastRefTime()
                    + ", new-t: " + serviceInfo.getLastRefTime());
            }

			// 更新本地缓存
            serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);

			/****************比较并处理oldHostMap 和当前 serviceInfo的不同,并合并处理start**************/
            Map<String, Instance> oldHostMap = new HashMap<String, Instance>(oldService.getHosts().size());
            for (Instance host : oldService.getHosts()) {
                oldHostMap.put(host.toInetAddr(), host);
            }

            Map<String, Instance> newHostMap = new HashMap<String, Instance>(serviceInfo.getHosts().size());
            for (Instance host : serviceInfo.getHosts()) {
                newHostMap.put(host.toInetAddr(), host);
            }

            Set<Instance> modHosts = new HashSet<Instance>();
            Set<Instance> newHosts = new HashSet<Instance>();
            Set<Instance> remvHosts = new HashSet<Instance>();

            List<Map.Entry<String, Instance>> newServiceHosts = new ArrayList<Map.Entry<String, Instance>>(
                newHostMap.entrySet());
            for (Map.Entry<String, Instance> entry : newServiceHosts) {
                Instance host = entry.getValue();
                String key = entry.getKey();
                if (oldHostMap.containsKey(key) && !StringUtils.equals(host.toString(),
                    oldHostMap.get(key).toString())) {
                    modHosts.add(host);
                    continue;
                }

                if (!oldHostMap.containsKey(key)) {
                    newHosts.add(host);
                }
            }

            for (Map.Entry<String, Instance> entry : oldHostMap.entrySet()) {
                Instance host = entry.getValue();
                String key = entry.getKey();
                if (newHostMap.containsKey(key)) {
                    continue;
                }

                if (!newHostMap.containsKey(key)) {
                    remvHosts.add(host);
                }

            }

            if (newHosts.size() > 0) {
                changed = true;
                NAMING_LOGGER.info("new ips(" + newHosts.size() + ") service: "
                    + serviceInfo.getKey() + " -> " + JSON.toJSONString(newHosts));
            }

            if (remvHosts.size() > 0) {
                changed = true;
                NAMING_LOGGER.info("removed ips(" + remvHosts.size() + ") service: "
                    + serviceInfo.getKey() + " -> " + JSON.toJSONString(remvHosts));
            }

            if (modHosts.size() > 0) {
                changed = true;
                NAMING_LOGGER.info("modified ips(" + modHosts.size() + ") service: "
                    + serviceInfo.getKey() + " -> " + JSON.toJSONString(modHosts));
            }

            serviceInfo.setJsonFromServer(json);
			/****************比较并处理oldHostMap 和当前 serviceInfo的不同,并合并处理end**************/

			// 判断是否存在更新
            if (newHosts.size() > 0 || remvHosts.size() > 0 || modHosts.size() > 0) {
            	// 存在更新,发送服务更新事件,通知所有事件监听
                eventDispatcher.serviceChanged(serviceInfo);
                // 更新磁盘缓存
                DiskCache.write(serviceInfo, cacheDir);
            }

        } else {
			// oldService为空,直接更新本地缓存,发送服务更新更新事件,更新磁盘缓存
            changed = true;
            NAMING_LOGGER.info("init new ips(" + serviceInfo.ipCount() + ") service: " + serviceInfo.getKey() + " -> " + JSON
                .toJSONString(serviceInfo.getHosts()));
            // 更新本地缓存
            serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
            // 发送服务更新事件
            eventDispatcher.serviceChanged(serviceInfo);
            serviceInfo.setJsonFromServer(json);
            // 更新磁盘缓存
            DiskCache.write(serviceInfo, cacheDir);
        }

        MetricsMonitor.getServiceInfoMapSizeMonitor().set(serviceInfoMap.size());

        if (changed) {
            NAMING_LOGGER.info("current ips:(" + serviceInfo.ipCount() + ") service: " + serviceInfo.getKey() +
                " -> " + JSON.toJSONString(serviceInfo.getHosts()));
        }

        return serviceInfo;
    }
}

2.4.4 hostReactor.scheduleUpdateIfAbsent

定时更新任务:添加一个UpdateTask,每隔一段时间,发送请求到服务端,更新本地缓存、磁盘缓存等。

public class HostReactor {
	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);
        }
    }
}

2.4.5 UpdateTask(HostReactor的内部类)

更新任务,用于保证本地缓存和Nacos服务端数据一致性。

public class HostReactor {
	public class UpdateTask implements Runnable {
        long lastRefTime = Long.MAX_VALUE;
        private String clusters;
        private String serviceName;

        public UpdateTask(String serviceName, String clusters) {
            this.serviceName = serviceName;
            this.clusters = clusters;
        }

        @Override
        public void run() {
            try {
                ServiceInfo serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));

				// 为空直接更新
                if (serviceObj == null) {
                	// 立即更新服务信息
                    updateServiceNow(serviceName, clusters);
                    // 循环添加UpdateTask   DEFAULT_DELAY=1000
                    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
                    // 已经被更新过,仅仅调用一次url=/nacos/v1/ns/instance/list       GET请求
                    refreshOnly(serviceName, clusters);
                }

                // 循环添加UpdateTask
                executor.schedule(this, serviceObj.getCacheMillis(), TimeUnit.MILLISECONDS);
				
				// 最后更新时间
                lastRefTime = serviceObj.getLastRefTime();
            } catch (Throwable e) {
                NAMING_LOGGER.warn("[NA] failed to update serviceName: " + serviceName, e);
            }

        }
    }
}

2.4.6 hostReactor.getServiceInfoDirectlyFromServer

该方法是直接通过NamingProxy发送请求到服务器,获取服务信息返回

url=/nacos/v1/ns/instance/list GET请求

public class HostReactor {
	public ServiceInfo getServiceInfoDirectlyFromServer(final String serviceName, final String clusters) throws NacosException {
		// 发送url=/nacos/v1/ns/instance/list       GET请求
        String result = serverProxy.queryList(serviceName, clusters, 0, false);
        if (StringUtils.isNotEmpty(result)) {
        	// 返回结果集
            return JSON.parseObject(result, ServiceInfo.class);
        }
        return null;
    }
}

2.4.6 服务端接受客户端请求返回

客户端通过发送 url=/nacos/v1/ns/instance/list GET请求,查询当前服务信息,服务端收到请求,处理如下:

@RestController
@RequestMapping(UtilsAndCommons.NACOS_NAMING_CONTEXT + "/instance")
public class InstanceController {
	
	/**
	 * 根据条件获取服务信息
	 */
	@RequestMapping(value = "/list", method = RequestMethod.GET)
    public JSONObject list(HttpServletRequest request) throws Exception {
        String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID,
            Constants.DEFAULT_NAMESPACE_ID);

        String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
        String agent = request.getHeader("Client-Version");
        if (StringUtils.isBlank(agent)) {
            agent = request.getHeader("User-Agent");
        }
        String clusters = WebUtils.optional(request, "clusters", StringUtils.EMPTY);
        String clientIP = WebUtils.optional(request, "clientIP", StringUtils.EMPTY);
        
        // UDP通信端口
        Integer udpPort = Integer.parseInt(WebUtils.optional(request, "udpPort", "0"));
        String env = WebUtils.optional(request, "env", StringUtils.EMPTY);
        boolean isCheck = Boolean.parseBoolean(WebUtils.optional(request, "isCheck", "false"));

        String app = WebUtils.optional(request, "app", StringUtils.EMPTY);

        String tenant = WebUtils.optional(request, "tid", StringUtils.EMPTY);

        boolean healthyOnly = Boolean.parseBoolean(WebUtils.optional(request, "healthyOnly", "false"));
		
		// 获取服务信息,如果UDP通信端口不为空,添加一个UDP通信客户端
        return doSrvIPXT(namespaceId, serviceName, agent, clusters, clientIP, udpPort, env, isCheck, app, tenant, healthyOnly);
    }
}

2.5 心跳检测机制

2.5.1 客户端发送心跳包(BeatReactor)

BeatReactor 主要是用来发送心跳包,服务端根据心跳时间,来判断当前服务上线、下线。

public class BeatReactor {
	public BeatReactor(NamingProxy serverProxy, int threadCount) {
        this.serverProxy = serverProxy;

        executorService = new ScheduledThreadPoolExecutor(threadCount, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setDaemon(true);
                thread.setName("com.alibaba.nacos.naming.beat.sender");
                return thread;
            }
        });
    }

	// 服务注册是,如果是临时节点,需要添加一个心跳信息
    public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
        NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
        dom2Beat.put(buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort()), beatInfo);
        
        // 执行心跳任务,beatInfo.getPeriod():心跳间隔
        executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
        
        MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
    }

	// Beat Task
	class BeatTask implements Runnable {

        BeatInfo beatInfo;

        public BeatTask(BeatInfo beatInfo) {
            this.beatInfo = beatInfo;
        }

        @Override
        public void run() {
            if (beatInfo.isStopped()) {
                return;
            }

			// 调用NamingProxy 中的sendBeat方法,发送心跳包,url=/instance/beat
            long result = serverProxy.sendBeat(beatInfo);
			
			// 下一次发送心跳时间
            long nextTime = result > 0 ? result : beatInfo.getPeriod();
            // 创建一个心跳延时任务
            executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);
        }
    }
}

2.5.2 服务端心跳检查(ClientBeatCheckTask)

在Service 服务对象中,存在一个 ClientBeatCheckTask 客户端心跳检查任务。

用于检查和更新临时实例的状态,如果它们已过期则将其删除。

public class ClientBeatCheckTask implements Runnable {

    private Service service;

    public ClientBeatCheckTask(Service service) {
        this.service = service;
    }


    @JSONField(serialize = false)
    public PushService getPushService() {
        return SpringContext.getAppContext().getBean(PushService.class);
    }

    @JSONField(serialize = false)
    public DistroMapper getDistroMapper() {
        return SpringContext.getAppContext().getBean(DistroMapper.class);
    }

    public GlobalConfig getGlobalConfig() {
        return SpringContext.getAppContext().getBean(GlobalConfig.class);
    }

    public String taskKey() {
        return service.getName();
    }

    @Override
    public void run() {
        try {
        	// 当前服务是否已经发行、注册实例
            if (!getDistroMapper().responsible(service.getName())) {
                return;
            }

			// 获取当前服务所有短暂实例
            List<Instance> instances = service.allIPs(true);

            // first set health status of instances:
            for (Instance instance : instances) {
            	// 当前时间 - 最后一次心跳时间 >   当前实例心跳超时时间
            	// true:已经失效  
                if (System.currentTimeMillis() - instance.getLastBeat() > instance.getInstanceHeartBeatTimeOut()) {
                	// 是否已被标记
                    if (!instance.isMarked()) {
                        if (instance.isHealthy()) {
                        	// 设置健康状态
                            instance.setHealthy(false);
                            Loggers.EVT_LOG.info("{POS} {IP-DISABLED} valid: {}:{}@{}@{}, region: {}, msg: client timeout after {}, last beat: {}",
                                instance.getIp(), instance.getPort(), instance.getClusterName(), service.getName(),
                                UtilsAndCommons.LOCALHOST_SITE, instance.getInstanceHeartBeatTimeOut(), instance.getLastBeat());
                            // 发送 ServiceChangeEvent 服务信息变化时间事件
                            getPushService().serviceChanged(service);
                            // 发送 InstanceHeartbeatTimeoutEvent 实例心跳超时事件
                            SpringContext.getAppContext().publishEvent(new InstanceHeartbeatTimeoutEvent(this, instance));
                        }
                    }
                }
            }

            if (!getGlobalConfig().isExpireInstance()) {
                return;
            }

            // then remove obsolete instances:
            // 删除过时的实例
            for (Instance instance : instances) {

                if (instance.isMarked()) {
                    continue;
                }

            	// 当前时间 - 最后一次心跳时间 >   当前ip超时删除时间
                if (System.currentTimeMillis() - instance.getLastBeat() > instance.getIpDeleteTimeout()) {
                    // delete instance
                    Loggers.SRV_LOG.info("[AUTO-DELETE-IP] service: {}, ip: {}", service.getName(), JSON.toJSONString(instance));
                    // 删除实例:调用本地实例删除请求
                    deleteIP(instance);
                }
            }

        } catch (Exception e) {
            Loggers.SRV_LOG.warn("Exception while processing client beat time out.", e);
        }

    }


    // 删除实例:调用本地实例删除请求
    private void deleteIP(Instance instance) {

        try {
            NamingProxy.Request request = NamingProxy.Request.newRequest();
            request.appendParam("ip", instance.getIp())
                .appendParam("port", String.valueOf(instance.getPort()))
                .appendParam("ephemeral", "true")
                .appendParam("clusterName", instance.getClusterName())
                .appendParam("serviceName", service.getName())
                .appendParam("namespaceId", service.getNamespaceId());

            String url = "http://127.0.0.1:" + RunningConfig.getServerPort() + RunningConfig.getContextPath()
                + UtilsAndCommons.NACOS_NAMING_CONTEXT + "/instance?" + request.toUrl();

            // delete instance asynchronously:
            HttpClient.asyncHttpDelete(url, null, null, new AsyncCompletionHandler() {
                @Override
                public Object onCompleted(Response response) throws Exception {
                    if (response.getStatusCode() != HttpURLConnection.HTTP_OK) {
                        Loggers.SRV_LOG.error("[IP-DEAD] failed to delete ip automatically, ip: {}, caused {}, resp code: {}",
                            instance.toJSON(), response.getResponseBody(), response.getStatusCode());
                    }
                    return null;
                }
            });

        } catch (Exception e) {
            Loggers.SRV_LOG.error("[IP-DEAD] failed to delete ip automatically, ip: {}, error: {}", instance.toJSON(), e);
        }
    }
}

2.6 服务列表变化通知

2.6.1 客户端定时发送更新请求(UpdateTask)

更新任务,用于保证本地缓存和Nacos服务端数据一致性。

public class HostReactor {
	public class UpdateTask implements Runnable {
        long lastRefTime = Long.MAX_VALUE;
        private String clusters;
        private String serviceName;

        public UpdateTask(String serviceName, String clusters) {
            this.serviceName = serviceName;
            this.clusters = clusters;
        }

        @Override
        public void run() {
            try {
                ServiceInfo serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));

				// 为空直接更新
                if (serviceObj == null) {
                	// 立即更新服务信息
                    updateServiceNow(serviceName, clusters);
                    // 循环添加UpdateTask   DEFAULT_DELAY=1000
                    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
                    // 已经被更新过,仅仅调用一次url=/nacos/v1/ns/instance/list       GET请求
                    refreshOnly(serviceName, clusters);
                }

                // 循环添加UpdateTask
                executor.schedule(this, serviceObj.getCacheMillis(), TimeUnit.MILLISECONDS);
				
				// 最后更新时间
                lastRefTime = serviceObj.getLastRefTime();
            } catch (Throwable e) {
                NAMING_LOGGER.warn("[NA] failed to update serviceName: " + serviceName, e);
            }

        }
    }
}

2.6.2 服务信息发现变化时,主动通知

当服务信息发现变化时(服务注册、服务注销、心跳超时等),Nacos Server端会发送一个 ServiceChangeEvent 事件,PushService 收到该事件后,会基于UDP通知所有客户端。
Nacos-注册中心原理解析_第3张图片

@Component
public class PushService implements ApplicationContextAware, ApplicationListener<ServiceChangeEvent> {
	
	// 客户端UDP连接缓存
    private static ConcurrentMap<String, ConcurrentMap<String, PushClient>> clientMap
        = new ConcurrentHashMap<String, ConcurrentMap<String, PushClient>>();

	/**
	 * 监听 ServiceChangeEvent  事件
	 */
	@Override
    public void onApplicationEvent(ServiceChangeEvent event) {
        Service service = event.getService();
        String serviceName = service.getName();
        String namespaceId = service.getNamespaceId();

        Future future = udpSender.schedule(new Runnable() {
            @Override
            public void run() {
                try {
                    Loggers.PUSH.info(serviceName + " is changed, add it to push queue.");
                    // 获取当前服务对应的 PushClient
                    ConcurrentMap<String, PushClient> clients = clientMap.get(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName));
                    if (MapUtils.isEmpty(clients)) {
                        return;
                    }

                    Map<String, Object> cache = new HashMap<>(16);
                    long lastRefTime = System.nanoTime();
                    for (PushClient client : clients.values()) {
                    	// 判断当前连接事件为"僵尸"连接:无效连接
                        if (client.zombie()) {
                            Loggers.PUSH.debug("client is zombie: " + client.toString());
                            clients.remove(client.toString());
                            Loggers.PUSH.debug("client is zombie: " + client.toString());
                            continue;
                        }

                        Receiver.AckEntry ackEntry;
                        Loggers.PUSH.debug("push serviceName: {} to client: {}", serviceName, client.toString());
                        // 获取推送缓存key
                        String key = getPushCacheKey(serviceName, client.getIp(), client.getAgent());
						
                        byte[] compressData = null;
                        Map<String, Object> data = null;
                        if (switchDomain.getDefaultPushCacheMillis() >= 20000 && cache.containsKey(key)) {
                            org.javatuples.Pair pair = (org.javatuples.Pair) cache.get(key);
                            compressData = (byte[]) (pair.getValue0());
                            data = (Map<String, Object>) pair.getValue1();

                            Loggers.PUSH.debug("[PUSH-CACHE] cache hit: {}:{}", serviceName, client.getAddrStr());
                        }
						
						// 准备(构建)请求报文
                        if (compressData != null) {
                            ackEntry = prepareAckEntry(client, compressData, data, lastRefTime);
                        } else {
                            ackEntry = prepareAckEntry(client, prepareHostsData(client), lastRefTime);
                            if (ackEntry != null) {
                                cache.put(key, new org.javatuples.Pair<>(ackEntry.origin.getData(), ackEntry.data));
                            }
                        }

                        Loggers.PUSH.info("serviceName: {} changed, schedule push for: {}, agent: {}, key: {}",
                            client.getServiceName(), client.getAddrStr(), client.getAgent(), (ackEntry == null ? null : ackEntry.key));

						// 基于UDP发送报文
                        udpPush(ackEntry);
                    }
                } catch (Exception e) {
                    Loggers.PUSH.error("[NACOS-PUSH] failed to push serviceName: {} to client, error: {}", serviceName, e);

                } finally {
                    futureMap.remove(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName));
                }

            }
        }, 1000, TimeUnit.MILLISECONDS);

        futureMap.put(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName), future);

    }
}

三、总结

Nacos-注册中心原理解析_第4张图片

你可能感兴趣的:(分布式微服务全家桶,java,服务发现,微服务)