Nacos服务注册与服务发现

目录

一、服务注册流程分析 (基于Nacos-0.9.0版本分析)

Step1:NacosServiceRegistry.register()

NacosNamingService.registerInstance()

1. 创建心跳信息

2. 调用注册方法

NamingProxy.registerService(String serviceName, Instance instance)

Step2:InstanceController.register()

ApiCommands.regService(HttpServletRequest request)

ApiCommands.addIP4Dom(HttpServletRequest request)

Step3:ApiCommands.addOrReplaceDom(HttpServletRequest request)

二、客户端服务发现的过程(基于Nacos-0.9.0版本分析)

服务发现NacosServerList

1. getServers()

2. NacosNamingService.selectInstances(serviceName, new ArrayList(), healthyOnly)

3. HostReactor.getServiceInfo(serviceName, StringUtils.join(clusters, ","), StringUtils.EMPTY, false)

4. HostReactor.updateServiceNow()

5. HostReactor.scheduleUpdateIfAbsent()

服务端处理服务发现请求

1. ApiCommands.srvIPXT()处理HTTP请求

2. VirtualClusterDomain.srvIPs(String clientIP, List clusters)

3. VirtualClusterDomain.allIPs(List clusters)

一、服务注册流程分析 (基于Nacos-0.9.0版本分析)

服务注册过程中主要做了下面这些事:

  • 创建VirtualClusterDomain对象;(每个服务对应一个对象)
  • 创建Cluster对象,并将它放在VirtualClusterDomain对象的Map clusterMap集合中;(每个集群名字对应一个Cluster对象)
  • 创建IpAddress,并将它存储在Cluster对象的raftIPs属性中(在服务发现过程中主要需要的数据就是这些IpAddress数据);

跟踪服务提供者服务注册的过程,发现

Step1:NacosServiceRegistry.register()

  • NacosDiscoveryProperties.namingServiceInstance()创建了一个 NamingService 实例
  • 封装了一个Instance实例
  • 调用NacosNamingService.registerInstance()方法进行注册

Nacos服务注册与服务发现_第1张图片

NacosNamingService.registerInstance()

Nacos服务注册与服务发现_第2张图片

1. 创建心跳信息

  • 客户端心跳信息:

创建了心跳信息,且加载到了心跳执行器BeatReactor中,BeatReactor 已经在NacosNamingService创建是就已经new 出来了,查看BeatReactor 的源码,可知,BeatReactor在创建时就启动了一个定时任务ScheduledExecutorService,这个定时任务,主要工作就是不停的通过http请求发送心跳数据到Nacos服务端:
1)向Nacos集群的随机一个节点的/v1/ns/api/clientBeat路径发送HTTP GET请求;
2)从json响应中取出clientBeatInterval字段值更新BeatReactor的clientBeatInterval属性值。
Nacos服务注册与服务发现_第3张图片
 

  • 服务端心跳

服务端的ApiCommands.clientBeat()处理/v1/ns/api/clientBeat请求:

  • 从DomainManager管理的serviceMap中获取namespaceId和serviceName对应的VirtualClusterDomain对象;
  • 然后执行VirtualClusterDomain.processClientBeat(clientBeat)方法
  • 最后返回下面的json数据:
    JSONObject result = new JSONObject();
    result.put("clientBeatInterval", Switch.getClientBeatInterval());

VirtualClusterDomain.processClientBeat(clientBeat)

public void processClientBeat(final RsInfo rsInfo) {
    clientBeatProcessor.setDomain(this);
    clientBeatProcessor.setRsInfo(rsInfo);
    HealthCheckReactor.scheduleNow(clientBeatProcessor); //立即执行ClientBeatProcessor线程任务
}

ClientBeatProcessor线程任务的处理逻辑:

public void process() {
    VirtualClusterDomain virtualClusterDomain = (VirtualClusterDomain) domain;
    if (!virtualClusterDomain.getEnableClientBeat()) {
        return;
    }
    String ip = rsInfo.getIp();
    String clusterName = rsInfo.getCluster();
    int port = rsInfo.getPort();
    //从VirtualClusterDomain的clusterMap中获取clusterName对应的Cluster对象
    Cluster cluster = virtualClusterDomain.getClusterMap().get(clusterName);
    //从Cluster对象中获取所有的IpAddress列表
    List ipAddresses = cluster.allIPs();
 
    for (IpAddress ipAddress: ipAddresses) {
        if (ipAddress.getIp().equals(ip) && ipAddress.getPort() == port) {
            Loggers.EVT_LOG.debug("[CLIENT-BEAT] refresh beat: {}", rsInfo.toString());
            //更新这个ip的最后心跳时间
            ipAddress.setLastBeat(System.currentTimeMillis());
            if (!ipAddress.isMarked()) {
                if (!ipAddress.isValid()) {
                    ipAddress.setValid(true);
                    Loggers.EVT_LOG.info("dom: {} {POS} {IP-ENABLED} valid: {}:{}@{}, region: {}, msg: client beat ok",
                        cluster.getDom().getName(), ip, port, cluster.getName(), DistroMapper.LOCALHOST_SITE);
                    PushService.domChanged(virtualClusterDomain.getNamespaceId(), domain.getName());
                }
            }
        }
    }
}

有个定时任务ClientBeatCheckTask每5s会检查是不是太久没心跳,如果没心跳就下线掉这个实例。

VirtualClusterDomain.init()方法中启动ClientBeatCheckTask客户端心跳检查任务:

public void init() {
    RaftCore.listen(this);
    HealthCheckReactor.scheduleCheck(clientBeatCheckTask);
    for (Map.Entry entry : clusterMap.entrySet()) {
        entry.getValue().init();
    }
}
public static void scheduleCheck(ClientBeatCheckTask task) {
    futureMap.putIfAbsent(task.taskKey(), EXECUTOR.scheduleWithFixedDelay(task, 5000, 5000, TimeUnit.MILLISECONDS));
}

ClientBeatCheckTask线程任务的处理逻辑:

  • 遍历VirtualClusterDomain的clusterMap集合中管理的所有IpAddress,(即不限于某个clusterName)
  • 如果某个IpAddress超过15没有收到心跳,则将它的valid属性置为false;
  • 如果某个IpAddress超过30没有收到心跳,通过Raft发布内容的方式通知Raft集群节点更新VirtualClusterDomain的clusterMap集合中对应clusterName的Cluster对象的raftIPs属性(即把要删除的IpAddress从raftIPs列表中删除)。

2. 调用注册方法

进一步调用注册方法serverProxy.registerService(serviceName, instance);

NamingProxy.registerService(String serviceName, Instance instance)

首先是通过NacosServiceRegistry.register()进行服务注册继续跟踪,后面调用了NamingProxy.registerService(String serviceName, Instance instance),在这个方法中服务提供者向server地址发送HTTP PUT请求:http://127.0.0.1:8848/nacos/v1/ns/instance;

Nacos服务注册与服务发现_第4张图片

Step2:InstanceController.register()

ApiCommands.regService(HttpServletRequest request)

server端的InstanceController.register()收到/v1/ns/instance请求,调用ApiCommands.regService(HttpServletRequest request)

1)创建IpAddress对象(大部分属性值都可以从http request获取)

Nacos服务注册与服务发现_第5张图片

2)(如果没有VirtualClusterDomain对象)然后执行ApiCommands.regDom(HttpServletRequest request)方法,DomainManager的serviceMap就有了对应的VirtualClusterDomain对象

Nacos服务注册与服务发现_第6张图片

3)向HttpServletRequest中追加下面的请求参数,并执行ApiCommands.addIP4Dom(HttpServletRequest request)

stringMap.put("dom", Arrays.asList(dom).toArray(new String[1]));
stringMap.put("ipList", Arrays.asList(JSON.toJSONString(Arrays.asList(ipAddress))).toArray(new String[1]));
stringMap.put("json", Arrays.asList("true").toArray(new String[1]));
stringMap.put("token", Arrays.asList(virtualClusterDomain.getToken()).toArray(new String[1]));

Nacos服务注册与服务发现_第7张图片   

ApiCommands.addIP4Dom(HttpServletRequest request)

整体思路就是:

先注册domain,再注册service。就是通过一个com.alibaba.nacos.naming.core.DomainsManager 管理了一个Map,key值是domainName (断点dom值为service name), value是com.alibaba.nacos.naming.core.Domain ,注册时,使用的实现类是 com.alibaba.nacos.naming.core.VirtualClusterDomain,注册的目的是将需要注册的服务Domain信息put到这个Map当中。

重要的类/接口:

  • com.alibaba.nacos.naming.raft.RaftListener 接口,维护了一个  List listeners 用户存放需要处理的RaftListener,DomainsManager 中有RaftListener的内部实现类,DomainsManager 在实例化对象时,将自己add到了listeners当中,待后续处理。Nacos服务注册与服务发现_第8张图片
  • com.alibaba.nacos.naming.core.DomainsManager  domain管理类,同时,内部有个RaftListener 的实现,上面所说put的逻辑也在这个实现当中。
  • com.alibaba.nacos.naming.core.VirtualClusterDomain 服务的Domain信息,也是RaftListener 的实现类
  • com.alibaba.nacos.naming.raft.RaftCore  维护着一个RaftListener的列表listeners,也维护着一个Notifier线程

    Notifier类里维护着一个BlockingQueue,Pair是Datum和ApplyAction的封装;
    Notifier.run方法的逻辑:
            从BlockingQueue取出一个Pair;
            遍历listeners中所有的RaftListener;
            如果Pair中Datum的key是RaftListener感兴趣的:
                    如果Pair中ApplyAction是CHANGE,则执行RaftListener#onChange方法
                    如果Pair中ApplyAction是DELETE,则执行RaftListener#onDelete方法

主要过程:

  1. addIP4Dom(HttpServletRequest request)->onAddIP4Dom()->easyAddIP4Dom()->RaftCore.onPublish()

    如果当前不是Raft leader节点,则向leader节点的/v1/ns/api/addIP4Dom路径发送HTTP POST请求,请求参数就是http request的请求参数。最终还是交给这个方法执行。
    向HttpServletRequest中继续追加下面的请求参数,并执行ApiCommands.onAddIP4Dom()

    proxyParams.put("clientIP", NetUtils.localServer());
    proxyParams.put("notify", "true");
    proxyParams.put("term", String.valueOf(RaftCore.getPeerSet().local().term)); //leader节点的任期
    proxyParams.put("timestamp", String.valueOf(timestamp)); //0L

    Nacos服务注册与服务发现_第9张图片
    Nacos服务注册与服务发现_第10张图片
    newIPs其实是最开始请求参数中的ipList来的,ipList在注册服务的时候出现过,如图:
    Nacos服务注册与服务发现_第11张图片


    Nacos服务注册与服务发现_第12张图片
    ipAddressMap.values() 其实就是旧的ip加上newIPs

  2. 注册dom的逻辑最终落在com.alibaba.nacos.naming.raft.RaftCore.onPublish(com.alibaba.nacos.naming.raft.Datum, com.alibaba.nacos.naming.raft.RaftPeer)方法中,将需要注册的信息通过Notifier的方法notifier.addTask(datum, Notifier.ApplyAction.CHANGE);加入到定时任务中进行执行。
    public void onPublish(Datum datum, RaftPeer source) throws Exception {
            RaftPeer local = peers.local();
            if (datum.value == null) {
                Loggers.RAFT.warn("received empty datum");
                throw new IllegalStateException("received empty datum");
            }
    
            if (!peers.isLeader(source.ip)) {
                Loggers.RAFT.warn("peer {} tried to publish data but wasn't leader, leader: {}",
                    JSON.toJSONString(source), JSON.toJSONString(getLeader()));
                throw new IllegalStateException("peer(" + source.ip + ") tried to publish " +
                    "data but wasn't leader");
            }
    
            if (source.term.get() < local.term.get()) {
                Loggers.RAFT.warn("out of date publish, pub-term: {}, cur-term: {}",
                    JSON.toJSONString(source), JSON.toJSONString(local));
                throw new IllegalStateException("out of date publish, pub-term:"
                    + source.term.get() + ", cur-term: " + local.term.get());
            }
    
            local.resetLeaderDue();
    
            // if data should be persistent, usually this is always true:
            if (KeyBuilder.matchPersistentKey(datum.key)) {
                raftStore.write(datum);
            }
    
            datums.put(datum.key, datum);
    
            if (isLeader()) {
                local.term.addAndGet(PUBLISH_TERM_INCREASE_COUNT);
            } else {
                if (local.term.get() + PUBLISH_TERM_INCREASE_COUNT > source.term.get()) {
                    //set leader term:
                    getLeader().term.set(source.term.get());
                    local.term.set(getLeader().term.get());
                } else {
                    local.term.addAndGet(PUBLISH_TERM_INCREASE_COUNT);
                }
            }
            raftStore.updateTerm(local.term.get());
    
            notifier.addTask(datum.key, ApplyAction.CHANGE);
    
            Loggers.RAFT.info("data added/updated, key={}, term={}", datum.key, local.term);
        }
    private BlockingQueue tasks = new LinkedBlockingQueue(1024 * 1024);
    
    public void addTask(String datumKey, ApplyAction action) {
    
          if (services.containsKey(datumKey) && action == ApplyAction.CHANGE) {
                    return;
          }
          if (action == ApplyAction.CHANGE) {
               services.put(datumKey, StringUtils.EMPTY);
          }
    
          Loggers.RAFT.info("add task {}", datumKey);
    
          tasks.add(Pair.with(datumKey, action));
    }

     

  3. 因为addTask()添加到RaftCore.Notifier.BlockingQueue的datum的key是com.alibaba.nacos.naming.iplist.##,调用接口RaftListener.onChange()方法,比如对应的VirtualClusterDomain对象的onChannge(key, value)方法,value是上面datum的value每个IpAddress对象都有一个clusterName属性;VirtualClusterDomain对象的clusterMap也有对应clusterName的Cluster对象;将含有相同clusterName的IpAddress更新到对应的Cluster的raftIPs属性中;

1)把上面创建的 VirtualClusterDomain对象放到DomainManager类的serviceMap中:Map>。

2)同时,会把VirtualClusterDomain对象注册到RaftCore的listeners列表中

3)对Dom信息进行update或者put,如果已经存在dom,则update,否则进行put。

Nacos服务注册与服务发现_第13张图片

Step3:ApiCommands.addOrReplaceDom(HttpServletRequest request)

Nacos服务注册与服务发现_第14张图片

二、客户端服务发现的过程(基于Nacos-0.9.0版本分析)

Nacos的负载均衡是基于Ribbon来做的。

Ribbon的负载均衡,主要通过LoadBalancerClient来实现的,而LoadBalancerClient具体交给了ILoadBalancer来处理,ILoadBalancer通过配置IRule、IPing等信息,并向EurekaClient获取注册列表的信息,并默认10秒一次向EurekaClient发送“ping”,进而检查是否更新服务列表,最后,得到注册列表后,ILoadBalancer根据IRule的策略进行负载均衡。

Ribbon最最底层也是实现spring cloud common包下的org.springframework.cloud.alibaba.nacos.ribbon.NacosServerList接口,主要是ServiceInstanceChooser下的继承接口:org.springframework.cloud.client.loadbalancer.LoadBalancerClient这是Ribbon实现负载均衡的父类接口,接下来一系列的接口实现最终会落到如何获取serverList这个问题是,答案在这个接口com.netflix.loadbalancer.ServerList,Nacos的实现就是:org.springframework.cloud.alibaba.nacos.ribbon.NacosServerList。

数据转换流程:

  • (服务端处理)IpAddress -> Json中的host信息 ->
  • (服务发现)ServiceInfo类的Instance -> NacosServer(继承自ribbon-loadbalancer的Server类)

服务发现NacosServerList

public class NacosServerList extends AbstractServerList {
 
	@Autowired
	private NacosDiscoveryProperties discoveryProperties;
 
	private String serviceId;
 
	public NacosServerList() {
	}
 
	public NacosServerList(String serviceId) {
		this.serviceId = serviceId;
	}
 
	@Override
	public List getInitialListOfServers() {
		return getServers();
	}
 
	@Override
	public List getUpdatedListOfServers() {
		return getServers();
	}
 
	private List getServers() {
		try {
			List instances = discoveryProperties.namingServiceInstance()
					.getAllInstances(serviceId);
			return instancesToServerList(instances);
		}
		catch (Exception e) {
			throw new IllegalStateException(
					"Can not get service instances from nacos, serviceId=" + serviceId,
					e);
		}
	}
 
	private List instancesToServerList(List instances) {
		List result = new ArrayList<>(instances.size());
		for (Instance instance : instances) {
			if (instance.isHealthy()) {
				result.add(new NacosServer(instance));
			}
		}
 
		return result;
	}
 
	public String getServiceId() {
		return serviceId;
	}
 
	@Override
	public void initWithNiwsConfig(IClientConfig iClientConfig) {
		this.serviceId = iClientConfig.getClientName();
	}
}

1. getServers()

  • NacosNamingService.selectInstances():拿到List数据
  • 将List转换成List数据,并返回。

2. NacosNamingService.selectInstances(serviceName, new ArrayList(), healthyOnly)

  • HostReactor.getServiceInfo():拿到ServiceInfo对象 
  • 从ServiceInfo对象中取出List hosts属性值并返回。

3. HostReactor.getServiceInfo(serviceName, StringUtils.join(clusters, ","), StringUtils.EMPTY, false)

HostReactor这个类中维护了:

  1. Map serviceInfoMap:维护了serverList的信息,key值是serverName,value是ServiceInfo
  2. 定时任务ScheduledExecutorService,后面会使用到。

getServiceInfo()方法主要逻辑是:

  • 先从已经存在的serviceInfoMap中通过serverName获取一个ServiceInfo,如果已经有了,需要再判断另一个updatingMap是否存在这个key,如果存在,在wait 5秒,这个时间是写死的。在返回ServiceInfo前,调用scheduleUpdateIfAbsent()方法更新。
  • 如果上面第一步serviceInfoMap不存在,则将传来的参数(serviceName, clusters, env)构建一个ServiceInfo,同时维护到serviceInfoMap和updatingMap中,同时根据allIPs这个参数的不同(我断点时为false)调用不同的接口去Nacos服务端拉取数据,通过方法updateService4AllIPNow() 和 updateServiceNow(),最后与上一步一样,调用scheduleUpdateIfAbsent()方法。
  • scheduleUpdateIfAbsent()方法。
  • 从serviceInfoMap中取出ServiceInfo并返回
    public List getAllInstances(String serviceName, List clusters) throws NacosException {
     
            ServiceInfo serviceInfo = hostReactor.getServiceInfo(serviceName, StringUtils.join(clusters, ","), StringUtils.EMPTY, false);
            List list;
            if (serviceInfo == null || CollectionUtils.isEmpty(list = serviceInfo.getHosts())) {
                return new ArrayList();
            }
            return list;
        }

     

4. HostReactor.updateServiceNow()

  • 向nacos server的/nacos/v1/ns/api/srvIPXT路径发送HTTP GET请求
  • 拿到HTTP Json响应结果,这个Json数据可以转换成ServiceInfo对象
  • 拿到ServiceInfo对象后,放到serviceInfoMap集合中

5. HostReactor.scheduleUpdateIfAbsent()

创建一个UpdateTask线程任务,每隔10s执行HostReactor.updateServiceNow方法,异步更新HostReactor的serviceInfoMap集合:如果serviceInfoMap中不存在需要的serverName,则,通过方法updateService4AllIPNow 和 updateServiceNow调用接口去获取,所以,serviceInfoMap并不是只在调用服务时才去获取更新,而是通过定时任务,通过心跳式的方式,不停的异步更新,10秒更新一次。

public void scheduleUpdateIfAbsent(String serviceName, String clusters, String env, boolean allIPs) {
        if (futureMap.get(ServiceInfo.getKey(serviceName, clusters, env, allIPs)) != null) {
            return;
        }
 
        synchronized (futureMap) {
            if (futureMap.get(ServiceInfo.getKey(serviceName, clusters, env, allIPs)) != null) {
                return;
            }
 
            ScheduledFuture future = addTask(new UpdateTask(serviceName, clusters, env, allIPs));
            futureMap.put(ServiceInfo.getKey(serviceName, clusters, env, allIPs), future);
        }
    }

 将一个UpdateTask 添加到了定时任务当中启动了,UpdateTask.run() 的代码:

@Override
        public void run() {
            try {
                ServiceInfo serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters, env, allIPs));
 
                if (serviceObj == null) {
                    if (allIPs) {
                        updateService4AllIPNow(serviceName, clusters, env);
                    } else {
                        updateServiceNow(serviceName, clusters, env);
                        executor.schedule(this, DEFAULT_DELAY, TimeUnit.MILLISECONDS);
                    }
                    return;
                }
 
                if (serviceObj.getLastRefTime() <= lastRefTime) {
                    if (allIPs) {
                        updateService4AllIPNow(serviceName, clusters, env);
                        serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters, env, true));
                    } else {
                        updateServiceNow(serviceName, clusters, env);
                        serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters, env));
                    }
 
                } else {
                    // if serviceName already updated by push, we should not override it
                    // since the push data may be different from pull through force push
                    refreshOnly(serviceName, clusters, env, allIPs);
                }
 
                executor.schedule(this, serviceObj.getCacheMillis(), TimeUnit.MILLISECONDS);
 
                lastRefTime = serviceObj.getLastRefTime();
            } catch (Throwable e) {
                LogUtils.LOG.warn("NA", "failed to update serviceName: " + serviceName, e);
            }
 
        }

服务端处理服务发现请求

处理HTTP请求/nacos/v1/ns/api/srvIPXT的过程:

1. ApiCommands.srvIPXT()处理HTTP请求

  • 从DomainManager管理的serviceMap中获取对应的VirtualClusterDomain对象,如果为空则抛异常;
  • VirtualClusterDomain.srvIPs():拿到List
  • 将每一个IpAddress转换成host信息,并填充其它信息到JSONObject中;
  • 返回JSONObject;
@RequestMapping("/srvIPXT")
    @ResponseBody
    public JSONObject srvIPXT(HttpServletRequest request) throws Exception {
 
        JSONObject result = new JSONObject();
 
        if (DistroMapper.getLocalhostIP().equals(UtilsAndCommons.LOCAL_HOST_IP)) {
            throw new Exception("invalid localhost ip: " + DistroMapper.getLocalhostIP());
        }
 
        String dom = BaseServlet.required(request, "dom");
 
        VirtualClusterDomain domObj = (VirtualClusterDomain) domainsManager.getDomain(dom);
        String agent = request.getHeader("Client-Version");
        String clusters = BaseServlet.optional(request, "clusters", StringUtils.EMPTY);
        String clientIP = BaseServlet.optional(request, "clientIP", StringUtils.EMPTY);
        Integer udpPort = Integer.parseInt(BaseServlet.optional(request, "udpPort", "0"));
        String env = BaseServlet.optional(request, "env", StringUtils.EMPTY);
        String error = BaseServlet.optional(request, "unconsistentDom", StringUtils.EMPTY);
        boolean isCheck = Boolean.parseBoolean(BaseServlet.optional(request, "isCheck", "false"));
 
        String app = BaseServlet.optional(request, "app", StringUtils.EMPTY);
 
        String tenant = BaseServlet.optional(request, "tid", StringUtils.EMPTY);
 
        boolean healthyOnly = Boolean.parseBoolean(BaseServlet.optional(request, "healthOnly", "false"));
 
        if (!StringUtils.isEmpty(error)) {
            Loggers.ROLE_LOG.info("ENV-NOT-CONSISTENT", error);
        }
 
        if (domObj == null) {
            throw new NacosException(NacosException.NOT_FOUND, "dom not found: " + dom);
        }
 
        checkIfDisabled(domObj);
 
        long cacheMillis = Switch.getCacheMillis(dom);
 
        // now try to enable the push
        try {
            if (udpPort > 0 && PushService.canEnablePush(agent)) {
                PushService.addClient(dom,
                        clusters,
                        agent,
                        new InetSocketAddress(clientIP, udpPort),
                        pushDataSource,
                        tenant,
                        app);
                cacheMillis = Switch.getPushCacheMillis(dom);
            }
        } catch (Exception e) {
            Loggers.SRV_LOG.error("VIPSRV-API", "failed to added push client", e);
            cacheMillis = Switch.getCacheMillis(dom);
        }
 
        List srvedIPs;
 
        srvedIPs = domObj.srvIPs(clientIP, Arrays.asList(StringUtils.split(clusters, ",")));
 
        if (CollectionUtils.isEmpty(srvedIPs)) {
            String msg = "no ip to serve for dom: " + dom;
 
            Loggers.SRV_LOG.debug(msg);
        }
 
        Map> ipMap = new HashMap<>(2);
        ipMap.put(Boolean.TRUE, new ArrayList());
        ipMap.put(Boolean.FALSE, new ArrayList());
 
        for (IpAddress ip : srvedIPs) {
            ipMap.get(ip.isValid()).add(ip);
        }
 
        if (isCheck) {
            result.put("reachProtectThreshold", false);
        }
 
        double threshold = domObj.getProtectThreshold();
 
        if ((float) ipMap.get(Boolean.TRUE).size() / srvedIPs.size() <= threshold) {
 
            Loggers.SRV_LOG.warn("protect threshold reached, return all ips, " +
                    "dom: " + dom);
            if (isCheck) {
                result.put("reachProtectThreshold", true);
            }
 
            ipMap.get(Boolean.TRUE).addAll(ipMap.get(Boolean.FALSE));
            ipMap.get(Boolean.FALSE).clear();
        }
 
        if (isCheck) {
            result.put("protectThreshold", domObj.getProtectThreshold());
            result.put("reachLocalSiteCallThreshold", false);
 
            return new JSONObject();
        }
 
        JSONArray hosts = new JSONArray();
 
        for (Map.Entry> entry : ipMap.entrySet()) {
            List ips = entry.getValue();
 
            if (healthyOnly && !entry.getKey()) {
                continue;
            }
 
            for (IpAddress ip : ips) {
                JSONObject ipObj = new JSONObject();
 
                ipObj.put("ip", ip.getIp());
                ipObj.put("port", ip.getPort());
                ipObj.put("valid", entry.getKey());
                ipObj.put("marked", ip.isMarked());
                ipObj.put("instanceId", ip.getInstanceId());
                ipObj.put("metadata", ip.getMetadata());
                ipObj.put("enabled", ip.isEnabled());
                ipObj.put("weight", ip.getWeight());
                ipObj.put("clusterName", ip.getClusterName());
                ipObj.put("serviceName", ip.getServiceName());
                hosts.add(ipObj);
 
            }
        }
 
        result.put("hosts", hosts);
 
        result.put("dom", dom);
        result.put("cacheMillis", cacheMillis);
        result.put("lastRefTime", System.currentTimeMillis());
        result.put("checksum", domObj.getChecksum() + System.currentTimeMillis());
        result.put("useSpecifiedURL", false);
        result.put("clusters", clusters);
        result.put("env", env);
        result.put("metadata", domObj.getMetadata());
        return result;
    }

2. VirtualClusterDomain.srvIPs(String clientIP, List clusters)

  • 因为clusters为空,所以会从VirtualClusterDomain的clusterMap集合中取出所有的cluster name;
  • VirtualClusterDomain.allIPs()

3. VirtualClusterDomain.allIPs(List clusters)

  • 从VirtualClusterDomain的clusterMap中取出每个clusterName对应的Cluster对象;
  • 把每个Cluster对象的raftIPs属性取出来,全部填充到List集合中,并返回;

 

 

参考:

https://www.jianshu.com/p/e1e3ecedc8b3

https://blog.csdn.net/Mr_Errol/article/details/84938993

https://blog.csdn.net/Mr_Errol/article/details/85089129

 

 

 

 

你可能感兴趣的:(Nacos)