Nacos1.X中对NacosNamingService的实现

Nacos服务注册与发现的实现原理图

服务注册与发现的功能:

  • 服务实例启动时注册到服务注册表、关闭时则注销(服务注册)
  • 服务注册中心根据服务实例定时发送的心跳包,实现健康检测(健康检查BeatReactor中的BeatTask)
  • 服务消费者可以通过查询服务注册表来获得可用的实例(服务发现)
  • 服务消费者定时拉取服务注册中心的服务实例数据(HostReactor中的UpdateTask)
  • 服务注册中心检测到服务提供者异常,主动通过UDP协议推送更新给服务消费者(PushReceiver)

NacosNamingService

Nacos Client包中的NamingService实现类为NacosNamingService,通过封装好的SDK供用户使用,来调用nacos对外暴露的OpenAPI

SDK方式只是提供了一种访问的封装,在底层仍然是基于HTTP协议完成请求的。

NamingService提供了以下方法:

  • registerInstance:注册实例

  • deregisterInstance:注销实例

  • getAllInstances:获取某一服务的所有实例

  • selectInstances:获取某一服务健康或不健康的实例

  • selectOneHealthyInstance:根据权重选择一个健康的实例

  • getServerStatus:检测服务端健康状态

  • subscribe:注册对某个服务的监听

  • unsubscribe:注销对某个服务的监听

  • getSubscribeServices:获取被监听的服务

  • getServicesOfServer:获取命名空间(namespace)下的所有服务名

NacosNamingService还初始化了其他核心类,外提供的方法都是委托给其他核心类处理的。按顺序将依次初始化NamingProxy、BeatReactor、HostReactor

  • NamingProxy:用于与Nacos服务端通信,注册服务、注销服务、发送心跳等都经由NamingProxy来请求服务端
  • BeatReactor:本地实例心跳,用于向Nacos服务端发送本地服务的心跳
  • HostReactor:用于从注册中心获取、保存、更新各服务实例信息
java 复制代码
public class NacosNamingService implements NamingService {
    private String namespace;
    private String endpoint;
    private String serverList;
    private String cacheDir;
    private String logName;
    private HostReactor hostReactor;
    //心跳包响应
    private BeatReactor beatReactor;
    //进行服务注册的带来,通过NamingProxy与Nacos Server进行最终的通信
    private NamingProxy serverProxy;

    public NacosNamingService(String serverList) throws NacosException {
        Properties properties = new Properties();
        properties.setProperty("serverAddr", serverList);
        this.init(properties);
    }

    public NacosNamingService(Properties properties) throws NacosException {
        this.init(properties);
    }

    private void init(Properties properties) throws NacosException {
        ValidatorUtils.checkInitParam(properties);
        this.namespace = InitUtils.initNamespaceForNaming(properties);
        InitUtils.initSerialization();
        this.initServerAddr(properties);
        InitUtils.initWebRootContext(properties);
        this.initCacheDir();
        this.initLogName(properties);
        //NamingService网络层代理
        this.serverProxy = new NamingProxy(this.namespace, this.endpoint, this.serverList, properties);
        //心跳包检测线程池
        this.beatReactor = new BeatReactor(this.serverProxy, this.initClientBeatThreadCount(properties));
        this.hostReactor = new HostReactor(this.serverProxy, this.beatReactor, this.cacheDir, this.isLoadCacheAtStart(properties), this.isPushEmptyProtect(properties), this.initPollingThreadCount(properties));
    }
     ......省略......

    //注册服务,委托NamingProxy处理
    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 = this.beatReactor.buildBeatInfo(groupedServiceName, instance);
            //将心跳包加到定时线程池中定时执行
            this.beatReactor.addBeatInfo(groupedServiceName, beatInfo);
        }
        //服务注册
        this.serverProxy.registerService(groupedServiceName, groupName, instance);
    }
    //注销服务,委托NamingProxy处理
    public void deregisterInstance(String serviceName, String groupName, Instance instance) throws NacosException {
        //如果是临时节点,则移除心跳包
        if (instance.isEphemeral()) {
            this.beatReactor.removeBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), instance.getIp(), instance.getPort());
        }
        //调用NamingProxy进行服务注销
        this.serverProxy.deregisterService(NamingUtils.getGroupedName(serviceName, groupName), instance);
    }

    //获取所有服务实例的方法,委托HostReactor处理
    public List<Instance> getAllInstances(String serviceName, String groupName, List<String> clusters, boolean subscribe) throws NacosException {
        ServiceInfo serviceInfo;
        // 如果该消费者订阅了这个服务,那么会先从本地维护的服务列表中获取,本地为空再从服务注册中心获取服务
        if (subscribe) {
            serviceInfo = this.hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));
        } else {
            // 否则实例会从服务中心进行获取
            serviceInfo = this.hostReactor.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));
        }

        List list;
        return (List)(serviceInfo != null && !CollectionUtils.isEmpty(list = serviceInfo.getHosts()) ? list : new ArrayList());
    }

    //获取健康(不健康)服务实例方法,委托HostReactor处理
    public List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy, boolean subscribe) throws NacosException {
        ServiceInfo serviceInfo;
        // 如果该消费者订阅了这个服务,那么会在本地维护一个服务列表,服务从本地获取
        if (subscribe) {
            serviceInfo = this.hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));
        } else {
            // 否则实例会从服务中心进行获取
            serviceInfo = this.hostReactor.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));
        }

        return this.selectInstances(serviceInfo, healthy);
    }

    private List<Instance> selectInstances(ServiceInfo serviceInfo, boolean healthy) {
        List list;
        if (serviceInfo != null && !CollectionUtils.isEmpty(list = serviceInfo.getHosts())) {
            Iterator iterator = list.iterator();

            while(true) {
                Instance instance;
                do {
                    if (!iterator.hasNext()) {
                        return list;
                    }

                    instance = (Instance)iterator.next();
                } while(healthy == instance.isHealthy() && instance.isEnabled() && instance.getWeight() > 0.0D);

                iterator.remove();
            }
        } else {
            return new ArrayList();
        }
    }

    //获取一个健康的实例,委托HostReactor处理
    public Instance selectOneHealthyInstance(String serviceName, String groupName, List<String> clusters, boolean subscribe) throws NacosException {
        return subscribe ? RandomByWeight.selectHost(this.hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","))) : RandomByWeight.selectHost(this.hostReactor.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ",")));
    }

    //监听服务实例,委托HostReactor处理
    public void subscribe(String serviceName, String groupName, List<String> clusters, EventListener listener) throws NacosException {
        this.hostReactor.subscribe(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","), listener);
    }
    //取消监听服务
    public void unsubscribe(String serviceName, String groupName, List<String> clusters, EventListener listener) throws NacosException {
        this.hostReactor.unSubscribe(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","), listener);
    }

    //查询服务列表
    public ListView<String> getServicesOfServer(int pageNo, int pageSize, String groupName, AbstractSelector selector) throws NacosException {
        return this.serverProxy.getServiceList(pageNo, pageSize, groupName, selector);
    }

    public List<ServiceInfo> getSubscribeServices() {
        return this.hostReactor.getSubscribeServices();
    }

    public String getServerStatus() {
        return this.serverProxy.serverHealthy() ? "UP" : "DOWN";
    }

    public BeatReactor getBeatReactor() {
        return this.beatReactor;
    }

    public void shutDown() throws NacosException {
        this.beatReactor.shutdown();
        this.hostReactor.shutdown();
        this.serverProxy.shutdown();
    }
}

NamingProxy

NamingProxy用于与Nacos服务端通信,注册服务、注销服务、发送心跳等都经由NamingProxy来请求服务端。

java 复制代码
public class NamingProxy implements Closeable {
    //Nacos自定义的RestTemplate
    private final NacosRestTemplate nacosRestTemplate = NamingHttpClientManager.getInstance().getNacosRestTemplate();
    //默认服务端口
    private static final int DEFAULT_SERVER_PORT = 8848;
    private int serverPort = 8848;
    //命名空间
    private final String namespaceId;
    private final String endpoint;
    private String nacosDomain;
    private List<String> serverList;
    private List<String> serversFromEndpoint = new ArrayList();
    private final SecurityProxy securityProxy;
    private long lastSrvRefTime = 0L;
    private final long vipSrvRefInterMillis;
    private final long securityInfoRefreshIntervalMills;
    private Properties properties;
    //刷新定时任务
    private ScheduledExecutorService executorService;
    //最大重试次数,默认值是3
    private int maxRetry;

    public NamingProxy(String namespaceId, String endpoint, String serverList, Properties properties) {
        this.vipSrvRefInterMillis = TimeUnit.SECONDS.toMillis(30L);
        this.securityInfoRefreshIntervalMills = TimeUnit.SECONDS.toMillis(5L);
        this.securityProxy = new SecurityProxy(properties, this.nacosRestTemplate);
        this.properties = properties;
        this.setServerPort(8848);
        this.namespaceId = namespaceId;
        this.endpoint = endpoint;
        this.maxRetry = ConvertUtils.toInt(properties.getProperty("namingRequestDomainMaxRetryCount", String.valueOf(3)));
        if (StringUtils.isNotEmpty(serverList)) {
            this.serverList = Arrays.asList(serverList.split(","));
            if (this.serverList.size() == 1) {
                this.nacosDomain = serverList;
            }
        }

        this.initRefreshTask();
    }
    //注册服务
    public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
        
        Map<String, String> params = new HashMap(16);
        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", JacksonUtils.toJson(instance.getMetadata()));
        // 把上述服务实例的一些必要参数保存到一个Map中,通过OpenAPI的方式发送注册请求
        this.reqApi(UtilAndComs.nacosUrlInstance, params, "POST");
    }
    //注销服务
    public void deregisterService(String serviceName, Instance instance) throws NacosException {
       
        Map<String, String> params = new HashMap(8);
        params.put("namespaceId", this.namespaceId);
        params.put("serviceName", serviceName);
        params.put("clusterName", instance.getClusterName());
        params.put("ip", instance.getIp());
        params.put("port", String.valueOf(instance.getPort()));
        params.put("ephemeral", String.valueOf(instance.isEphemeral()));
        this.reqApi(UtilAndComs.nacosUrlInstance, params, "DELETE");
    }

    //更新服务
    public void updateInstance(String serviceName, String groupName, Instance instance) throws NacosException {
       
        Map<String, String> params = new HashMap(8);
        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("enabled", String.valueOf(instance.isEnabled()));
        params.put("ephemeral", String.valueOf(instance.isEphemeral()));
        params.put("metadata", JacksonUtils.toJson(instance.getMetadata()));
        this.reqApi(UtilAndComs.nacosUrlInstance, params, "PUT");
    }

    public Service queryService(String serviceName, String groupName) throws NacosException {
        
        Map<String, String> params = new HashMap(3);
        params.put("namespaceId", this.namespaceId);
        params.put("serviceName", serviceName);
        params.put("groupName", groupName);
        String result = this.reqApi(UtilAndComs.nacosUrlService, params, "GET");
        return (Service)JacksonUtils.toObj(result, Service.class);
    }

    public void createService(Service service, AbstractSelector selector) throws NacosException {
        
        Map<String, String> params = new HashMap(6);
        params.put("namespaceId", this.namespaceId);
        params.put("serviceName", service.getName());
        params.put("groupName", service.getGroupName());
        params.put("protectThreshold", String.valueOf(service.getProtectThreshold()));
        params.put("metadata", JacksonUtils.toJson(service.getMetadata()));
        params.put("selector", JacksonUtils.toJson(selector));
        this.reqApi(UtilAndComs.nacosUrlService, params, "POST");
    }

    public boolean deleteService(String serviceName, String groupName) throws NacosException {
        
        Map<String, String> params = new HashMap(6);
        params.put("namespaceId", this.namespaceId);
        params.put("serviceName", serviceName);
        params.put("groupName", groupName);
        String result = this.reqApi(UtilAndComs.nacosUrlService, params, "DELETE");
        return "ok".equals(result);
    }

    public void updateService(Service service, AbstractSelector selector) throws NacosException {
       
        Map<String, String> params = new HashMap(6);
        params.put("namespaceId", this.namespaceId);
        params.put("serviceName", service.getName());
        params.put("groupName", service.getGroupName());
        params.put("protectThreshold", String.valueOf(service.getProtectThreshold()));
        params.put("metadata", JacksonUtils.toJson(service.getMetadata()));
        params.put("selector", JacksonUtils.toJson(selector));
        this.reqApi(UtilAndComs.nacosUrlService, params, "PUT");
    }

    public String queryList(String serviceName, String clusters, int udpPort, boolean healthyOnly) throws NacosException {
        Map<String, String> params = new HashMap(8);
        params.put("namespaceId", this.namespaceId);
        params.put("serviceName", serviceName);
        params.put("clusters", clusters);
        params.put("udpPort", String.valueOf(udpPort));
        params.put("clientIP", NetUtils.localIP());
        params.put("healthyOnly", String.valueOf(healthyOnly));
        return this.reqApi(UtilAndComs.nacosUrlBase + "/instance/list", params, "GET");
    }

    //发送心跳信息,通过心跳机制向服务端报告实例的健康状态
    public JsonNode sendBeat(BeatInfo beatInfo, boolean lightBeatEnabled) throws NacosException {
        if (LogUtils.NAMING_LOGGER.isDebugEnabled()) {
            LogUtils.NAMING_LOGGER.debug("[BEAT] {} sending beat to server: {}", this.namespaceId, beatInfo.toString());
        }

        Map<String, String> params = new HashMap(8);
        Map<String, String> bodyMap = new HashMap(2);
        if (!lightBeatEnabled) {
            bodyMap.put("beat", JacksonUtils.toJson(beatInfo));
        }

        params.put("namespaceId", this.namespaceId);
        params.put("serviceName", beatInfo.getServiceName());
        params.put("clusterName", beatInfo.getCluster());
        params.put("ip", beatInfo.getIp());
        params.put("port", String.valueOf(beatInfo.getPort()));
        String result = this.reqApi(UtilAndComs.nacosUrlBase + "/instance/beat", params, bodyMap, "PUT");
        return JacksonUtils.toObj(result);
    }

    public boolean serverHealthy() {
        try {
            String result = this.reqApi(UtilAndComs.nacosUrlBase + "/operator/metrics", new HashMap(2), "GET");
            JsonNode json = JacksonUtils.toObj(result);
            String serverStatus = json.get("status").asText();
            return "UP".equals(serverStatus);
        } catch (Exception var4) {
            return false;
        }
    }
    //查询服务列表
    public ListView<String> getServiceList(int pageNo, int pageSize, String groupName) throws NacosException {
        return this.getServiceList(pageNo, pageSize, groupName, (AbstractSelector)null);
    }

    public ListView<String> getServiceList(int pageNo, int pageSize, String groupName, AbstractSelector selector) throws NacosException {
        Map<String, String> params = new HashMap(4);
        params.put("pageNo", String.valueOf(pageNo));
        params.put("pageSize", String.valueOf(pageSize));
        params.put("namespaceId", this.namespaceId);
        params.put("groupName", groupName);
        if (selector != null) {
            switch(SelectorType.valueOf(selector.getType())) {
            case none:
            default:
                break;
            case label:
                ExpressionSelector expressionSelector = (ExpressionSelector)selector;
                params.put("selector", JacksonUtils.toJson(expressionSelector));
            }
        }

        String result = this.reqApi(UtilAndComs.nacosUrlBase + "/service/list", params, "GET");
        JsonNode json = JacksonUtils.toObj(result);
        ListView<String> listView = new ListView();
        listView.setCount(json.get("count").asInt());
        listView.setData((List)JacksonUtils.toObj(json.get("doms").toString(), new TypeReference<List<String>>() {
        }));
        return listView;
    }

    public String reqApi(String api, Map<String, String> params, String method) throws NacosException {
        return this.reqApi(api, params, Collections.EMPTY_MAP, method);
    }

    public String reqApi(String api, Map<String, String> params, Map<String, String> body, String method) throws NacosException {
        return this.reqApi(api, params, body, this.getServerList(), method);
    }

    public String reqApi(String api, Map<String, String> params, Map<String, String> body, List<String> servers, String method) throws NacosException {
        params.put("namespaceId", this.getNamespaceId());
        if (CollectionUtils.isEmpty(servers) && StringUtils.isBlank(this.nacosDomain)) {
            throw new NacosException(400, "no server available");
        } else {
            NacosException exception = new NacosException();
            if (StringUtils.isNotBlank(this.nacosDomain)) {
                int i = 0;

                while(i < this.maxRetry) {
                    try {
                        return this.callServer(api, params, body, this.nacosDomain, method);
                    } catch (NacosException var12) {
                        exception = var12;
                        if (LogUtils.NAMING_LOGGER.isDebugEnabled()) {
                            LogUtils.NAMING_LOGGER.debug("request {} failed.", this.nacosDomain, var12);
                        }

                        ++i;
                    }
                }
            } else {
                Random random = new Random(System.currentTimeMillis());
                int index = random.nextInt(servers.size());
                int i = 0;

                while(i < servers.size()) {
                    String server = (String)servers.get(index);

                    try {
                        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;
                    }
                }
            }

            throw new NacosException(exception.getErrCode(), "failed to req API:" + api + " after all servers(" + servers + ") tried: " + exception.getMessage());
        }
    }

    public String callServer(String api, Map<String, String> params, Map<String, String> body, String curServer) throws NacosException {
        return this.callServer(api, params, body, curServer, "GET");
    }

    public String callServer(String api, Map<String, String> params, Map<String, String> body, String curServer, String method) throws NacosException {
        long start = System.currentTimeMillis();
        long end = 0L;
        this.injectSecurityInfo(params);
        Header header = this.builderHeader();
        String url;
        if (!curServer.startsWith("https://") && !curServer.startsWith("http://")) {
            if (!IPUtil.containsPort(curServer)) {
                curServer = curServer + ":" + this.serverPort;
            }

            url = NamingHttpClientManager.getInstance().getPrefix() + curServer + api;
        } else {
            url = curServer + api;
        }

        try {
            HttpRestResult<String> restResult = this.nacosRestTemplate.exchangeForm(url, header, Query.newInstance().initParams(params), body, method, String.class);
            end = System.currentTimeMillis();
            MetricsMonitor.getNamingRequestMonitor(method, url, String.valueOf(restResult.getCode())).observe((double)(end - start));
            if (restResult.ok()) {
                return (String)restResult.getData();
            } else if (304 == restResult.getCode()) {
                return "";
            } else {
                throw new NacosException(restResult.getCode(), restResult.getMessage());
            }
        } catch (Exception var13) {
            LogUtils.NAMING_LOGGER.error("[NA] failed to request", var13);
            throw new NacosException(500, var13);
        }
    }

    private void injectSecurityInfo(Map<String, String> params) {
        if (StringUtils.isNotBlank(this.securityProxy.getAccessToken())) {
            params.put("accessToken", this.securityProxy.getAccessToken());
        }

        String ak = this.getAccessKey();
        String sk = this.getSecretKey();
        params.put("app", AppNameUtils.getAppName());
        if (StringUtils.isNotBlank(ak) && StringUtils.isNotBlank(sk)) {
            try {
                String signData = getSignData((String)params.get("serviceName"));
                String signature = SignUtil.sign(signData, sk);
                params.put("signature", signature);
                params.put("data", signData);
                params.put("ak", ak);
            } catch (Exception var6) {
                LogUtils.NAMING_LOGGER.error("inject ak/sk failed.", var6);
            }
        }

    }

    public String getAccessKey() {
        return this.properties == null ? SpasAdapter.getAk() : TemplateUtils.stringEmptyAndThenExecute(this.properties.getProperty("accessKey"), new Callable<String>() {
            public String call() {
                return SpasAdapter.getAk();
            }
        });
    }

    public String getSecretKey() {
        return this.properties == null ? SpasAdapter.getSk() : TemplateUtils.stringEmptyAndThenExecute(this.properties.getProperty("secretKey"), new Callable<String>() {
            public String call() throws Exception {
                return SpasAdapter.getSk();
            }
        });
    }

}

serverAddr和endpoint两种方式配置NacosServer地址

  • serverAddr方式是直接告诉客户端nacos服务端的IP;
  • endpoint是告诉客户端一个endpoint,客户端通过HTTP请求到endpoint查询nacos服务端IP列表,这里endpoint不是NacosServer的地址,而是获取NacosServer地址的连接点。
    NamingProxy会启动1个,用于定期调用refreshSrvIfNeed()方法更新Nacos服务端地址,默认间隔为30秒。
    refreshSrvIfNeed()方法对Nacos服务端地址的更新仅在使用endpoint的时候才会进行实际更新,如果是通过serverAddr配置的Nacos服务端地址,refreshSrvIfNeed()方法将不会进行任何操作。

NamingProxy中和endpoint方式有关的代码

java 复制代码
//初始化刷新定时任务
    private void initRefreshTask() {
        //初始化线程池
        this.executorService = new ScheduledThreadPoolExecutor(2, new ThreadFactory() {
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("com.alibaba.nacos.client.naming.updater");
                t.setDaemon(true);
                return t;
            }
        });
        this.refreshSrvIfNeed();
        this.securityProxy.login(this.getServerList());
        //设置定时刷新任务
        this.executorService.scheduleWithFixedDelay(new Runnable() {
            public void run() {
                NamingProxy.this.refreshSrvIfNeed();
            }
        }, 0L, this.vipSrvRefInterMillis, TimeUnit.MILLISECONDS);
        //
        this.executorService.scheduleWithFixedDelay(new Runnable() {
            public void run() {
                NamingProxy.this.securityProxy.login(NamingProxy.this.getServerList());
            }
        }, 0L, this.securityInfoRefreshIntervalMills, TimeUnit.MILLISECONDS);
    }

    //
    public List<String> getServerListFromEndpoint() {
        try {
            String urlString = "http://" + this.endpoint + "/nacos/serverlist";
            Header header = this.builderHeader();
            HttpRestResult<String> restResult = this.nacosRestTemplate.get(urlString, header, Query.EMPTY, String.class);
            if (!restResult.ok()) {
                throw new IOException("Error while requesting: " + urlString + "'. Server returned: " + restResult.getCode());
            } else {
                String content = (String)restResult.getData();
                List<String> list = new ArrayList();
                Iterator var6 = IoUtils.readLines(new StringReader(content)).iterator();

                while(var6.hasNext()) {
                    String line = (String)var6.next();
                    if (!line.trim().isEmpty()) {
                        list.add(line.trim());
                    }
                }

                return list;
            }
        } catch (Exception var8) {
            var8.printStackTrace();
            return null;
        }
    }
    //进行刷新
    private void refreshSrvIfNeed() {
        try {
            if (!CollectionUtils.isEmpty(this.serverList)) {
                return;
            }
            if (System.currentTimeMillis() - this.lastSrvRefTime < this.vipSrvRefInterMillis) {
                return;
            }

            List<String> list = this.getServerListFromEndpoint();
            if (CollectionUtils.isEmpty(list)) {
                throw new Exception("Can not acquire Nacos list");
            }

            if (!CollectionUtils.isEqualCollection(list, this.serversFromEndpoint)) {
            }

            this.serversFromEndpoint = list;
            this.lastSrvRefTime = System.currentTimeMillis();
        } catch (Throwable var2) {
            LogUtils.NAMING_LOGGER.warn("failed to update server list", var2);
        }

    }
    private List<String> getServerList() {
        List<String> snapshot = this.serversFromEndpoint;
        if (!CollectionUtils.isEmpty(this.serverList)) {
            snapshot = this.serverList;
        }

        return snapshot;
    }

BeatReactor

BeatReactor 是 Nacos 客户端用于维护服务实例心跳的核心组件,它的作用是确保客户端的服务实例在 Nacos 服务端保持可见和健康。

通过定期发送心跳请求,BeatReactor 可以让 Nacos 服务端知道某个服务实例依然在线,并在实例异常(例如断线或宕机)时及时清理掉不健康的实例。

成员变量Map<String, BeatInfo> dom2Beat中保存了需要发送的BeatInfo,key为{serviceName}#{ip}#{port},value为对应的BeatInfo。

BeatReactor维护一个定期执行线程任务的线程池,默认线程数为1~CPU核心数的一半,可由namingClientBeatThreadCount参数指定。

java 复制代码
public class BeatReactor implements Closeable {
    //定时任务线程池
    private final ScheduledExecutorService executorService;
    //与NacosServer通信的代理
    private final NamingProxy serverProxy;
    private boolean lightBeatEnabled;
    //所有需要发送心跳的服务实例及其对应的心跳信息
    public final Map<String, BeatInfo> dom2Beat;

    public BeatReactor(NamingProxy serverProxy) {
        this(serverProxy, UtilAndComs.DEFAULT_CLIENT_BEAT_THREAD_COUNT);
    }
    public BeatReactor(NamingProxy serverProxy, int threadCount) {
        this.lightBeatEnabled = false;
        this.dom2Beat = new ConcurrentHashMap();
        this.serverProxy = serverProxy;
        this.executorService = new ScheduledThreadPoolExecutor(threadCount, new ThreadFactory() {
            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) {
        //构造心跳包key
        String key = this.buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
        BeatInfo existBeat = null;
        //从dom2Beat移除该key,如果key对应的value不为空的话,表明该beatInfo已经存在了,则停止心跳检测
        if ((existBeat = (BeatInfo)this.dom2Beat.remove(key)) != null) {
            //停止心跳检测
            existBeat.setStopped(true);
        }
        //如果dom2Beat中不存在该key,则将key放到map中,并进行定时心跳检测
        this.dom2Beat.put(key, beatInfo);
        //定时任务线程池,定时执行里面的线程
        this.executorService.schedule(new BeatReactor.BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
        MetricsMonitor.getDom2BeatSizeMonitor().set((double)this.dom2Beat.size());
    }

    public void removeBeatInfo(String serviceName, String ip, int port) {
        //从心跳包map中删除对应的心跳包信息
        BeatInfo beatInfo = (BeatInfo)this.dom2Beat.remove(this.buildKey(serviceName, ip, port));
        //将心跳包状态设置为stop
        if (beatInfo != null) {
            beatInfo.setStopped(true);
            MetricsMonitor.getDom2BeatSizeMonitor().set((double)this.dom2Beat.size());
        }
    }
    //根据服务实例,构造心跳包
    public BeatInfo buildBeatInfo(Instance instance) {
        return this.buildBeatInfo(instance.getServiceName(), instance);
    }

    public BeatInfo buildBeatInfo(String groupedServiceName, Instance instance) {
        BeatInfo beatInfo = new BeatInfo();
        beatInfo.setServiceName(groupedServiceName);
        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());
        return beatInfo;
    }

    public String buildKey(String serviceName, String ip, int port) {
        return serviceName + "#" + ip + "#" + port;
    }

    public void shutdown() throws NacosException {
        String className = this.getClass().getName();
        ThreadUtils.shutdownThreadPool(this.executorService, LogUtils.NAMING_LOGGER);
    }
    
}

BeatInfo 心跳信息

代表一个服务实例的心跳信息,包括服务名、IP 地址、端口、权重等

java 复制代码
public class BeatInfo {
    private String serviceName;
    private String ip;
    private int port;
    private double weight;
    // 其他字段
}

BeatTask(BeatReactor的内部类)

负责定期执行心跳任务的类,每个 BeatTask 对应一个服务实例。

当任务运行时,它会调用 NamingProxy.sendBeat 向 Nacos 服务端发送心跳。

java 复制代码
//心跳包发送线程
    class BeatTask implements Runnable {
        BeatInfo beatInfo;

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

        public void run() {
            //如果心跳包检查没有停止,则发送心跳包
            if (!this.beatInfo.isStopped()) {
                long nextTime = this.beatInfo.getPeriod();

                try {
                    //发送心跳包
                    JsonNode result = BeatReactor.this.serverProxy.sendBeat(this.beatInfo, BeatReactor.this.lightBeatEnabled);
                    long interval = result.get("clientBeatInterval").asLong();
                    boolean lightBeatEnabled = false;
                    if (result.has("lightBeatEnabled")) {
                        lightBeatEnabled = result.get("lightBeatEnabled").asBoolean();
                    }

                    BeatReactor.this.lightBeatEnabled = lightBeatEnabled;
                    if (interval > 0L) {
                        nextTime = interval;
                    }

                    int code = 10200;
                    if (result.has("code")) {
                        code = result.get("code").asInt();
                    }
                    //如果该实例没有在注册中心注册,则进行注册
                    if (code == 20404) {
                        Instance instance = new Instance();
                        instance.setPort(this.beatInfo.getPort());
                        instance.setIp(this.beatInfo.getIp());
                        instance.setWeight(this.beatInfo.getWeight());
                        instance.setMetadata(this.beatInfo.getMetadata());
                        instance.setClusterName(this.beatInfo.getCluster());
                        instance.setServiceName(this.beatInfo.getServiceName());
                        instance.setInstanceId(instance.getInstanceId());
                        instance.setEphemeral(true);

                        try {
                           //注册服务
             BeatReactor.this.serverProxy.registerService(this.beatInfo.getServiceName(), NamingUtils.getGroupName(this.beatInfo.getServiceName()), instance);
                        } catch (Exception var15) {
                           //log日志
                        }
                    }
                } catch (NacosException var16) {
                    //log日志
                } catch (Exception var17) {
                    //log日志
                } finally {
                    //将线程再次放到定时任务线程池中执行下次的心跳包发送
                    BeatReactor.this.executorService.schedule(BeatReactor.this.new BeatTask(this.beatInfo), nextTime, TimeUnit.MILLISECONDS);
                }
            }
        }
    }

HostReactor

HostReactor用于获取、保存、更新各Service实例信息。

成员变量Map<String, ServiceInfo> serviceInfoMap中保存了已获取到的服务的信息,key为{服务名}@@{集群名}。

HostReactor会启动名为com.alibaba.nacos.client.naming.updater的线程来更新服务信息,默认线程数为1~CPU核心数的一半,可由namingPollingThreadCount参数指定。

定时任务UpdateTask会根据服务的cacheMillis值定时更新服务信息,默认值为10秒。该定时任务会在获取某一服务信息时创建,保存在成员变量Map<String, ScheduledFuture<?>> futureMap中。

java 复制代码
public class HostReactor implements Closeable {
    private static final long DEFAULT_DELAY = 1000L;
    private static final long UPDATE_HOLD_INTERVAL = 5000L;
    private final Map<String, ScheduledFuture<?>> futureMap;
    // 本地已存在的服务列表,key是服务名称,value是ServiceInfo
    private final Map<String, ServiceInfo> serviceInfoMap;
    // 正在更新的实例集合
    private final Map<String, Object> updatingMap;
    // 接收NacosServer端主动推送服务信息端
    private final PushReceiver pushReceiver;
    // 与NacosServer保持心跳服务
    private final BeatReactor beatReactor;
    //底层与NacosServer端通信端代理类
    private final NamingProxy serverProxy;
    //
    private final FailoverReactor failoverReactor;
    //本地缓存端路径
    private final String cacheDir;
    private final boolean pushEmptyProtection;
    // 定时任务(负责服务列表的实时更新)
    private final ScheduledExecutorService executor;
    //实例变化通知者,负责管理服务的订阅信息,并进行回调
    private final InstancesChangeNotifier notifier;

    public HostReactor(NamingProxy serverProxy, BeatReactor beatReactor, String cacheDir) {
        this(serverProxy, beatReactor, cacheDir, false, false, UtilAndComs.DEFAULT_POLLING_THREAD_COUNT);
    }

    public HostReactor(NamingProxy serverProxy, BeatReactor beatReactor, String cacheDir, boolean loadCacheAtStart, boolean pushEmptyProtection, int pollingThreadCount) {
        this.futureMap = new HashMap();
        this.executor = new ScheduledThreadPoolExecutor(pollingThreadCount, new ThreadFactory() {
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setDaemon(true);
                thread.setName("com.alibaba.nacos.client.naming.updater");
                return thread;
            }
        });
        this.beatReactor = beatReactor;
        this.serverProxy = serverProxy;
        this.cacheDir = cacheDir;
        if (loadCacheAtStart) {
            this.serviceInfoMap = new ConcurrentHashMap(DiskCache.read(this.cacheDir));
        } else {
            this.serviceInfoMap = new ConcurrentHashMap(16);
        }

        this.pushEmptyProtection = pushEmptyProtection;
        this.updatingMap = new ConcurrentHashMap();
        this.failoverReactor = new FailoverReactor(this, cacheDir);
        this.pushReceiver = new PushReceiver(this);
        this.notifier = new InstancesChangeNotifier();
        NotifyCenter.registerToPublisher(InstancesChangeEvent.class, 16384);
        NotifyCenter.registerSubscriber(this.notifier);
    }

    public Map<String, ServiceInfo> getServiceInfoMap() {
        return this.serviceInfoMap;
    }
    //增加定时刷新服务任务
    public synchronized ScheduledFuture<?> addTask(HostReactor.UpdateTask task) {
        return this.executor.schedule(task, 1000L, TimeUnit.MILLISECONDS);
    }
    //订阅服务,serviceName服务名称,clusters集群列表,EventLintener回调Listener
    public void subscribe(String serviceName, String clusters, EventListener eventListener) {
        //给该服务增加监听器,服务发生变化后进行回调
        this.notifier.registerListener(serviceName, clusters, eventListener);
        //将该服务添加到HostReactor的定时任务中,定时刷新
        this.getServiceInfo(serviceName, clusters);
    }
    //取消订阅服务,serviceName服务名称,clusters集群列表,EventLintener回调Listener
    public void unSubscribe(String serviceName, String clusters, EventListener eventListener) {
        this.notifier.deregisterListener(serviceName, clusters, eventListener);
    }

    public List<ServiceInfo> getSubscribeServices() {
        return this.notifier.getSubscribeServices();
    }
    //处理从注册中心获取到的JSON格式的服务实例,并更新到本地serviceInfoMap中
    public ServiceInfo processServiceJson(String json) {
        ServiceInfo serviceInfo = (ServiceInfo)JacksonUtils.toObj(json, ServiceInfo.class);
        String serviceKey = serviceInfo.getKey();
        if (serviceKey == null) {
            return null;
        } else {
            ServiceInfo oldService = (ServiceInfo)this.serviceInfoMap.get(serviceKey);
            if (this.pushEmptyProtection && !serviceInfo.validate()) {
                return oldService;
            } else {
                boolean changed = false;
                if (oldService != null) {
                    if (oldService.getLastRefTime() > serviceInfo.getLastRefTime()) {
                        LogUtils.NAMING_LOGGER.warn("out of date data received, old-t: " + oldService.getLastRefTime() + ", new-t: " + serviceInfo.getLastRefTime());
                    }

                    this.serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
                    Map<String, Instance> oldHostMap = new HashMap(oldService.getHosts().size());
                    Iterator var7 = oldService.getHosts().iterator();

                    while(var7.hasNext()) {
                        Instance host = (Instance)var7.next();
                        oldHostMap.put(host.toInetAddr(), host);
                    }

                    Map<String, Instance> newHostMap = new HashMap(serviceInfo.getHosts().size());
                    Iterator var17 = serviceInfo.getHosts().iterator();

                    while(var17.hasNext()) {
                        Instance host = (Instance)var17.next();
                        newHostMap.put(host.toInetAddr(), host);
                    }

                    Set<Instance> modHosts = new HashSet();
                    Set<Instance> newHosts = new HashSet();
                    Set<Instance> remvHosts = new HashSet();
                    List<Entry<String, Instance>> newServiceHosts = new ArrayList(newHostMap.entrySet());
                    Iterator var12 = newServiceHosts.iterator();

                    while(true) {
                        Entry entry;
                        Instance host;
                        String key;
                        while(var12.hasNext()) {
                            entry = (Entry)var12.next();
                            host = (Instance)entry.getValue();
                            key = (String)entry.getKey();
                            if (oldHostMap.containsKey(key) && !StringUtils.equals(host.toString(), ((Instance)oldHostMap.get(key)).toString())) {
                                modHosts.add(host);
                            } else if (!oldHostMap.containsKey(key)) {
                                newHosts.add(host);
                            }
                        }

                        var12 = oldHostMap.entrySet().iterator();

                        while(var12.hasNext()) {
                            entry = (Entry)var12.next();
                            host = (Instance)entry.getValue();
                            key = (String)entry.getKey();
                            if (!newHostMap.containsKey(key) && !newHostMap.containsKey(key)) {
                                remvHosts.add(host);
                            }
                        }

                        if (newHosts.size() > 0) {
                            changed = true;
                        }

                        if (remvHosts.size() > 0) {
                            changed = true;
                        }

                        if (modHosts.size() > 0) {
                            changed = true;
                            this.updateBeatInfo(modHosts);
                        }

                        serviceInfo.setJsonFromServer(json);
                        if (newHosts.size() > 0 || remvHosts.size() > 0 || modHosts.size() > 0) {
                            NotifyCenter.publishEvent(new InstancesChangeEvent(serviceInfo.getName(), serviceInfo.getGroupName(), serviceInfo.getClusters(), serviceInfo.getHosts()));
                            DiskCache.write(serviceInfo, this.cacheDir);
                        }
                        break;
                    }
                } else {
                    changed = true;
     
                    this.serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
                    NotifyCenter.publishEvent(new InstancesChangeEvent(serviceInfo.getName(), serviceInfo.getGroupName(), serviceInfo.getClusters(), serviceInfo.getHosts()));
                    serviceInfo.setJsonFromServer(json);
                    DiskCache.write(serviceInfo, this.cacheDir);
                }

                MetricsMonitor.getServiceInfoMapSizeMonitor().set((double)this.serviceInfoMap.size());
                if (changed) {
                   //记录日志
                }
                return serviceInfo;
            }
        }
    }

    private void updateBeatInfo(Set<Instance> modHosts) {
        Iterator var2 = modHosts.iterator();

        while(var2.hasNext()) {
            Instance instance = (Instance)var2.next();
            String key = this.beatReactor.buildKey(instance.getServiceName(), instance.getIp(), instance.getPort());
            if (this.beatReactor.dom2Beat.containsKey(key) && instance.isEphemeral()) {
                BeatInfo beatInfo = this.beatReactor.buildBeatInfo(instance);
                this.beatReactor.addBeatInfo(instance.getServiceName(), beatInfo);
            }
        }

    }
    //从本地缓存中获取服务实例信息
    private ServiceInfo getServiceInfo0(String serviceName, String clusters) {
        String key = ServiceInfo.getKey(serviceName, clusters);
        return (ServiceInfo)this.serviceInfoMap.get(key);
    }
    //直接从服务注册中心获取服务
    public ServiceInfo getServiceInfoDirectlyFromServer(String serviceName, String clusters) throws NacosException {
        String result = this.serverProxy.queryList(serviceName, clusters, 0, false);
        return StringUtils.isNotEmpty(result) ? (ServiceInfo)JacksonUtils.toObj(result, ServiceInfo.class) : null;
    }
    //获取服务,先从本地获取,本地没有,则进行维护,并从注册中心更新最新服务信息
    public ServiceInfo getServiceInfo(String serviceName, String clusters) {
        
        String key = ServiceInfo.getKey(serviceName, clusters);
        if (this.failoverReactor.isFailoverSwitch()) {
            return this.failoverReactor.getService(key);
        } else {
            // 1.先通过serverName即服务名获得一个serviceInfo
            ServiceInfo serviceObj = this.getServiceInfo0(serviceName, clusters);
            if (null == serviceObj) {
                //如果没有serviceInfo,则通过传进来的参数new出一个新的serviceInfo对象,并且同时维护到本地Map和更新Map
                serviceObj = new ServiceInfo(serviceName, clusters);
                this.serviceInfoMap.put(serviceObj.getKey(), serviceObj);
                this.updatingMap.put(serviceName, new Object());
                // 2.updateServiceNow(),立刻去Nacos服务端拉取该服务最新实例列表,更新serviceInfoMap
                this.updateServiceNow(serviceName, clusters);
                this.updatingMap.remove(serviceName);
            } else if (this.updatingMap.containsKey(serviceName)) {
                synchronized(serviceObj) {
                    try {
                        serviceObj.wait(5000L);
                    } catch (InterruptedException var8) {
                        
                    }
                }
            }
            // 3.定时更新实例信息
            this.scheduleUpdateIfAbsent(serviceName, clusters);
            // 4.最后返回服务实例数据(前面已经进行了更新)
            return (ServiceInfo)this.serviceInfoMap.get(serviceObj.getKey());
        }
    }
    //立即从注册中心拉取该服务最新实例列表,并更新到本地
    private void updateServiceNow(String serviceName, String clusters) {
        try {
            this.updateService(serviceName, clusters);
        } catch (NacosException var4) {  
        }
    }
    //通过定时任务,每10秒去更新一次数据
    public void scheduleUpdateIfAbsent(String serviceName, String clusters) {
        if (this.futureMap.get(ServiceInfo.getKey(serviceName, clusters)) == null) {
            synchronized(this.futureMap) {
                if (this.futureMap.get(ServiceInfo.getKey(serviceName, clusters)) == null) {
                    //创建一个UpdateTask的更新线程任务,每10秒去异步更新集合数据
                    ScheduledFuture<?> future = this.addTask(new HostReactor.UpdateTask(serviceName, clusters));
                    this.futureMap.put(ServiceInfo.getKey(serviceName, clusters), future);
                }
            }
        }
    }
    //从注册中心拉取该服务最新实例列表,并更新到本地
    //这里需要特别关注,client端通过该方法将client端udpPort传给Nacos Server端
    public void updateService(String serviceName, String clusters) throws NacosException {
        ServiceInfo oldService = this.getServiceInfo0(serviceName, clusters);
        boolean var12 = false;
        try {
            var12 = true;
            //从注册中心查询服务下的实例列表
            String result = this.serverProxy.queryList(serviceName, clusters, this.pushReceiver.getUdpPort(), false);
            if (StringUtils.isNotEmpty(result)) {
                //处理从注册中心获取到的服务实例JSON数据,更新本地服务列表
                this.processServiceJson(result);
                var12 = false;
            } else {
                var12 = false;
            }
        } finally {
            if (var12) {
                if (oldService != null) {
                    synchronized(oldService) {
                        oldService.notifyAll();
                    }
                }
            }
        }
        if (oldService != null) {
            synchronized(oldService) {
                oldService.notifyAll();
            }
        }
    }
    //仅仅执行刷新,从Nacos注册中心获取服务,但不刷新本地列表
    public void refreshOnly(String serviceName, String clusters) {
        try {
            this.serverProxy.queryList(serviceName, clusters, this.pushReceiver.getUdpPort(), false);
        } catch (Exception var4) {    
        }
    }

    public void shutdown() throws NacosException {
        String className = this.getClass().getName();
        LogUtils.NAMING_LOGGER.info("{} do shutdown begin", className);
        ThreadUtils.shutdownThreadPool(this.executor, LogUtils.NAMING_LOGGER);
        this.pushReceiver.shutdown();
        this.failoverReactor.shutdown();
        NotifyCenter.deregisterSubscriber(this.notifier);
        LogUtils.NAMING_LOGGER.info("{} do shutdown stop", className);
    }
}

获取服务实例流程

更新服务实例流程UpdateTask

UpdateTask(Nacos拉模式)

HostReactor的内部类,用于定期从NacosServer端拉取最新的服务信息。

java 复制代码
//更新任务线程
    public class UpdateTask implements Runnable {
        long lastRefTime = 9223372036854775807L;
        private final String clusters;
        private final String serviceName;
        private int failCount = 0;

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

        private void incFailCount() {
            int limit = 6;
            if (this.failCount != limit) {
                ++this.failCount;
            }
        }

        private void resetFailCount() {
            this.failCount = 0;
        }

        public void run() {
            long delayTime = 1000L;

            try {
                //从本地缓存中获取服务实例列表
                ServiceInfo serviceObj = (ServiceInfo)HostReactor.this.serviceInfoMap.get(ServiceInfo.getKey(this.serviceName, this.clusters));
                //如果服务为空,则更新本地服务列表
                if (serviceObj == null) {
                    HostReactor.this.updateService(this.serviceName, this.clusters);
                    return;
                }
                // 过期服务(服务的最新更新时间小于等于缓存刷新时间),从注册中心重新查询
                if (serviceObj.getLastRefTime() <= this.lastRefTime) {
                    HostReactor.this.updateService(this.serviceName, this.clusters);
                    serviceObj = (ServiceInfo)HostReactor.this.serviceInfoMap.get(ServiceInfo.getKey(this.serviceName, this.clusters));
                } else {
                    HostReactor.this.refreshOnly(this.serviceName, this.clusters);
                }
                 // 刷新更新时间 
                this.lastRefTime = serviceObj.getLastRefTime();
                // 判断该注册的Service是否被订阅,如果没有订阅则不再执行
                if (!HostReactor.this.notifier.isSubscribed(this.serviceName, this.clusters) && !HostReactor.this.futureMap.containsKey(ServiceInfo.getKey(this.serviceName, this.clusters))) {
                    return;
                }

                if (CollectionUtils.isEmpty(serviceObj.getHosts())) {
                    this.incFailCount();
                    return;
                }
                // 下次更新缓存时间设置,默认为10秒
                delayTime = serviceObj.getCacheMillis();
                //任务运行成功,重置失败次数为0
                this.resetFailCount();
            } catch (Throwable var7) {
                //任务执行失败,失败次数+1
                this.incFailCount();
            } finally {
                //取delayTime<<failCount的值与60000L之间的小值,作为下次运行的时间间隔
                //下次调度刷新时间,下次执行的时间与failCount有关 
                HostReactor.this.executor.schedule(this, Math.min(delayTime << this.failCount, 60000L), TimeUnit.MILLISECONDS);
            }

        }
    }

PushReceiver(Nacos推模式)

PushReceiver用于接收Nacos服务端的推送,初始化时会创建DatagramSocket使用UDP的方式接收Nacos Server端推送的最新服务信息。

PushReceiver使用的UDP协议的客户端的端口udpPort会在UpdateTask任务中,调用updateService的方法中获取,并且传给NacosServer端;

NacosServer会保存该udpPort端口,当Client订阅的服务信息发生变化时,就会主动推送最新信息给Client端。

java 复制代码
public class PushReceiver implements Runnable, Closeable {
    private static final Charset UTF_8 = Charset.forName("UTF-8");
    private static final int UDP_MSS = 65536;
    private ScheduledExecutorService executorService;
    private DatagramSocket udpSocket;
    private HostReactor hostReactor;
    private volatile boolean closed = false;

    public static String getPushReceiverUdpPort() {
        return System.getenv("push.receiver.udp.port");
    }

    public PushReceiver(HostReactor hostReactor) {
        try {
            this.hostReactor = hostReactor;
            String udpPort = getPushReceiverUdpPort();
            if (StringUtils.isEmpty(udpPort)) {
                this.udpSocket = new DatagramSocket();
            } else {
                this.udpSocket = new DatagramSocket(new InetSocketAddress(Integer.parseInt(udpPort)));
            }

            this.executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r);
                    thread.setDaemon(true);
                    thread.setName("com.alibaba.nacos.naming.push.receiver");
                    return thread;
                }
            });
            this.executorService.execute(this);
        } catch (Exception var3) {
            LogUtils.NAMING_LOGGER.error("[NA] init udp socket failed", var3);
        }

    }

    public void run() {
        while(!this.closed) {
            try {
                byte[] buffer = new byte[65536];
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                this.udpSocket.receive(packet);
                String json = (new String(IoUtils.tryDecompress(packet.getData()), UTF_8)).trim();
                LogUtils.NAMING_LOGGER.info("received push data: " + json + " from " + packet.getAddress().toString());
                PushReceiver.PushPacket pushPacket = (PushReceiver.PushPacket)JacksonUtils.toObj(json, PushReceiver.PushPacket.class);
                String ack;
                if (!"dom".equals(pushPacket.type) && !"service".equals(pushPacket.type)) {
                    if ("dump".equals(pushPacket.type)) {
                        ack = "{\"type\": \"dump-ack\", \"lastRefTime\": \"" + pushPacket.lastRefTime + "\", \"data\":\"" + StringUtils.escapeJavaScript(JacksonUtils.toJson(this.hostReactor.getServiceInfoMap())) + "\"}";
                    } else {
                        ack = "{\"type\": \"unknown-ack\", \"lastRefTime\":\"" + pushPacket.lastRefTime + "\", \"data\":\"\"}";
                    }
                } else {
                    //调用HostReactor处理收到的json数据
                    this.hostReactor.processServiceJson(pushPacket.data);
                    ack = "{\"type\": \"push-ack\", \"lastRefTime\":\"" + pushPacket.lastRefTime + "\", \"data\":\"\"}";
                }

                this.udpSocket.send(new DatagramPacket(ack.getBytes(UTF_8), ack.getBytes(UTF_8).length, packet.getSocketAddress()));
            } catch (Exception var6) {
                if (this.closed) {
                    return;
                }

                LogUtils.NAMING_LOGGER.error("[NA] error while receiving push data", var6);
            }
        }

    }

    public void shutdown() throws NacosException {
        String className = this.getClass().getName();
        LogUtils.NAMING_LOGGER.info("{} do shutdown begin", className);
        ThreadUtils.shutdownThreadPool(this.executorService, LogUtils.NAMING_LOGGER);
        this.closed = true;
        this.udpSocket.close();
        LogUtils.NAMING_LOGGER.info("{} do shutdown stop", className);
    }

    public int getUdpPort() {
        return this.udpSocket.getLocalPort();
    }

    public static class PushPacket {
        public String type;
        public long lastRefTime;
        public String data;

        public PushPacket() {
        }
    }
}

InstancesChangeNotifier

该对象负责保存服务实例的监听,当服务实例发生变化的时候,负责进行通知,回调监听器方法

java 复制代码
public class InstancesChangeNotifier extends Subscriber<InstancesChangeEvent> {
    //监听器map
    private final Map<String, ConcurrentHashSet<EventListener>> listenerMap = new ConcurrentHashMap();
    private final Object lock = new Object();

    public InstancesChangeNotifier() {
    }
    //注册服务监听器
    public void registerListener(String serviceName, String clusters, EventListener listener) {
        String key = ServiceInfo.getKey(serviceName, clusters);
        ConcurrentHashSet<EventListener> eventListeners = (ConcurrentHashSet)this.listenerMap.get(key);
        if (eventListeners == null) {
            synchronized(this.lock) {
                eventListeners = (ConcurrentHashSet)this.listenerMap.get(key);
                if (eventListeners == null) {
                    eventListeners = new ConcurrentHashSet();
                    this.listenerMap.put(key, eventListeners);
                }
            }
        }

        eventListeners.add(listener);
    }
    //取消注册服务监听器
    public void deregisterListener(String serviceName, String clusters, EventListener listener) {
        String key = ServiceInfo.getKey(serviceName, clusters);
        ConcurrentHashSet<EventListener> eventListeners = (ConcurrentHashSet)this.listenerMap.get(key);
        if (eventListeners != null) {
            eventListeners.remove(listener);
            if (CollectionUtils.isEmpty(eventListeners)) {
                this.listenerMap.remove(key);
            }

        }
    }
    //判断是否被订阅
    public boolean isSubscribed(String serviceName, String clusters) {
        String key = ServiceInfo.getKey(serviceName, clusters);
        ConcurrentHashSet<EventListener> eventListeners = (ConcurrentHashSet)this.listenerMap.get(key);
        return CollectionUtils.isNotEmpty(eventListeners);
    }

    public List<ServiceInfo> getSubscribeServices() {
        List<ServiceInfo> serviceInfos = new ArrayList();
        Iterator var2 = this.listenerMap.keySet().iterator();

        while(var2.hasNext()) {
            String key = (String)var2.next();
            serviceInfos.add(ServiceInfo.fromKey(key));
        }

        return serviceInfos;
    }
    //服务实例变化事件
    public void onEvent(InstancesChangeEvent event) {
        String key = ServiceInfo.getKey(event.getServiceName(), event.getClusters());
        //获取该服务的订阅者
        ConcurrentHashSet<EventListener> eventListeners = (ConcurrentHashSet)this.listenerMap.get(key);
        if (!CollectionUtils.isEmpty(eventListeners)) {
            Iterator var4 = eventListeners.iterator();

            while(true) {
                //循环该服务的订阅者,调用订阅者的回调方法
                while(var4.hasNext()) {
                    final EventListener listener = (EventListener)var4.next();
                    final Event namingEvent = this.transferToNamingEvent(event);
                    //如果该listener继承了Nacos定义的AbstractEventListener,并且executor不为空,则通过executor以新的线程的方式回调监听onEvent方法
                    if (listener instanceof AbstractEventListener && ((AbstractEventListener)listener).getExecutor() != null) {
                        ((AbstractEventListener)listener).getExecutor().execute(new Runnable() {
                            public void run() {
                                listener.onEvent(namingEvent);
                            }
                        });
                    } 
                    //否则直接调用该监听器的onEvent方法
                    else {
                        listener.onEvent(namingEvent);
                    }
                }
                //回调结束,返回
                return;
            }
        }
    }
    //转换事件
    private Event transferToNamingEvent(InstancesChangeEvent instancesChangeEvent) {
        return new NamingEvent(instancesChangeEvent.getServiceName(), instancesChangeEvent.getGroupName(), instancesChangeEvent.getClusters(), instancesChangeEvent.getHosts());
    }

    public Class<? extends com.alibaba.nacos.common.notify.Event> subscribeType() {
        return InstancesChangeEvent.class;
    }
}
相关推荐
考虑考虑2 小时前
Jpa使用union all
java·spring boot·后端
用户3721574261353 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊4 小时前
Java学习第22天 - 云原生与容器化
java
渣哥6 小时前
原来 Java 里线程安全集合有这么多种
java
间彧6 小时前
Spring Boot集成Spring Security完整指南
java
间彧6 小时前
Spring Secutiy基本原理及工作流程
java
Java水解7 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆9 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学10 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole10 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端