通过 Ribbon 查询 Nacos 服务实例

开发 前端
Nacos 提供了开放 API 可通过 /nacos/v1/ns/instance/list 获取服务列表。如果我们采用 spring-cloud 方式去获取服务,最终会通过 Nacos Client + loadbalancer 的方式进行客户端负载均衡。

本文转载自微信公众号「运维开发故事」,作者老郑。转载本文请联系运维开发故事公众号。

Nacos 服务列表管理

Nacos 提供了开放 API 可通过 /nacos/v1/ns/instance/list 获取服务列表。如果我们采用 spring-cloud 方式去获取服务,最终会通过 Nacos Client + loadbalancer 的方式进行客户端负载均衡。

Ribbon 源码解析

Ribbon 简介

Spring Cloud Ribbon 是 Netflix Ribbon 实现的一套客户端负载均衡工具 简单的说,Ribbon 是 Netflix 发布的开源项目,主要功能是提供客户端的复杂算法和服务调用。 Ribbon 客户端组件提供一系列完善的配置项如超时、重试等。简单的说,就是配置文件中列出 load Balancer (简称 LB)后面所有的机器,Ribbon 会自动的帮助你基于某种规则(如简单轮询,随机链接等)去链接这些机器。我们很容易使用 Ribbon 自定义的负载均衡算法。

Ribbon 使用

首先需要定义 RestTemplate 使用 Ribbon 策略;

  1. @Configuration 
  2. public class RestTemplateConfig { 
  3.     @LoadBalanced 
  4.     @Bean 
  5.     public RestTemplate restTemplate() { 
  6.         return new RestTemplate(); 
  7.     } 

本地使用 RestTemplate 调用远程接口;

  1. @Autowired 
  2. private RestTemplate restTemplate; 
  3. @RequestMapping(value = "/echo/{id}", method = RequestMethod.GET) 
  4. public String echo(@PathVariable Long id) { 
  5.     return restTemplate.getForObject("http://member-service/member/get/" + id, String.class); 

Ribbon 源码分析

RestTemplate 继承 InterceptingHttpAccessor 通过 interceptors 字段接受 HttpRequestInterceptor 请求拦截器。对于 Ribbion 初始化类是 RibbonAutoConfiguration 中的, 它在 spring-cloud-netflix-ribbon 中定义。但是它在初始化之前,又需要加载 RibbonAutoConfiguration 配置,它是在 spring-cloud-common 中。具体的代码如下:

  1. @Configuration(proxyBeanMethods = false
  2. // 工程中一定存在 RestTemplate 类 
  3. @ConditionalOnClass(RestTemplate.class) 
  4. // 容器中一定存在 LoadBalancerClient 类 Bean 实例 
  5. @ConditionalOnBean(LoadBalancerClient.class) 
  6. @EnableConfigurationProperties(LoadBalancerRetryProperties.class) 
  7. public class LoadBalancerAutoConfiguration { 
  8.  
  9.    // 获取 Spring 容器中所有的 RestTemplate 实例 
  10.    @LoadBalanced 
  11.    @Autowired(required = false
  12.    private List<RestTemplate> restTemplates = Collections.emptyList(); 
  13.  
  14.    // 获取 Spring 容器中 LoadBalancerRequestTransformer 实例 
  15.    @Autowired(required = false
  16.    private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList(); 
  17.  
  18.    // 在 Bean 初始化完成后会调用 afterSingletonsInstantiated 方法 
  19.    // 这里是一个 lambda 表达式方式的实现, 主要是为 restTemplate 实例设置 RestTemplateCustomizer 
  20.    @Bean 
  21.    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated( 
  22.          final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) { 
  23.       return () -> restTemplateCustomizers.ifAvailable(customizers -> { 
  24.          for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { 
  25.             for (RestTemplateCustomizer customizer : customizers) { 
  26.                customizer.customize(restTemplate); 
  27.             } 
  28.          } 
  29.       }); 
  30.    } 
  31.  
  32.    // LoadBalancerRequestFactory 工厂类 
  33.    // 主要是用来提供 LoadBalancerClient 实例和 LoadBalancerRequestTransformer 
  34.    @Bean 
  35.    @ConditionalOnMissingBean 
  36.    public LoadBalancerRequestFactory loadBalancerRequestFactory( 
  37.          LoadBalancerClient loadBalancerClient) { 
  38.       return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers); 
  39.    } 
  40.  
  41.    // LoadBalancerInterceptor 拦截器  
  42.    @Configuration(proxyBeanMethods = false
  43.    @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate"
  44.    static class LoadBalancerInterceptorConfig { 
  45.  
  46.       // 创建默认的拦截器 LoadBalancerInterceptor 的实例 
  47.       @Bean 
  48.       public LoadBalancerInterceptor ribbonInterceptor( 
  49.             LoadBalancerClient loadBalancerClient, 
  50.             LoadBalancerRequestFactory requestFactory) { 
  51.          return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); 
  52.       } 
  53.  
  54.       // 如果没有 RestTemplateCustomizer 实例才会创建 
  55.       // 这里就就会为咱们所有的  restTemplate 实例添加 loadBalancerInterceptor 拦截器 
  56.       @Bean 
  57.       @ConditionalOnMissingBean 
  58.       public RestTemplateCustomizer restTemplateCustomizer( 
  59.             final LoadBalancerInterceptor loadBalancerInterceptor) { 
  60.          return restTemplate -> { 
  61.             List<ClientHttpRequestInterceptor> list = new ArrayList<>( 
  62.                   restTemplate.getInterceptors()); 
  63.             list.add(loadBalancerInterceptor); 
  64.             restTemplate.setInterceptors(list); 
  65.          }; 
  66.       } 
  67.  
  68.    } 
  69.   // ... 
  70.  

针对下面的代码我们可以总结一下:

如果需要使用负载均衡,工程下面必须要有 RestTemplate 类, 然后Spring 容器中要有 LoadBalancerClient 的实例。

LoadBalancerClient 在 spring-cloud-netflix-ribbon 中只有一个实现类: RibbonLoadBalancerClient

利用 Spring 的 SmartInitializingSingleton 拓展点,在 restTemplateCustomizer() 中为所有的 RestTemplate 添加 LoadBalancerInterceptor 拦截器

其实 LoadBalancer 的本质就是通过拦截器。利用 RestTemplate 的拓展点来实现请求服务的负载均衡。

LoadBalancerInterceptor

LoadBalancerInterceptor 拦截器会将请求交给 LoadBalancerClient 去处理,首先会选择一个 ILoadBalancer 的实现来处理获取和选择服务,然后通过 serviceName 和负载均衡算法去选择 Server 对象。最后执行请求。

  1. public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor { 
  2.  
  3.    // 负载均衡 
  4.    private LoadBalancerClient loadBalancer; 
  5.  
  6.    // 构建请求  
  7.    private LoadBalancerRequestFactory requestFactory; 
  8.  
  9.    // ... 
  10.  
  11.    @Override 
  12.    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, 
  13.          final ClientHttpRequestExecution execution) throws IOException { 
  14.       final URI originalUri = request.getURI(); 
  15.       String serviceName = originalUri.getHost();  
  16.       return this.loadBalancer.execute(serviceName, 
  17.             this.requestFactory.createRequest(request, body, execution)); 
  18.    } 
  19.  

RibbonLoadBalancerClient

我们通过跟踪 this.loadBalancer.execute 代码发现。最终所有的请求都交由 RibbonLoadBalancerClient 去处理。它实现了。LoadBalancerClient 接口, 代码如下:

  1. public interface ServiceInstanceChooser { 
  2.  
  3.   // 通过 serviceId 选择具体的服务实例 
  4.   ServiceInstance choose(String serviceId); 
  5.  
  6.  
  7. public interface LoadBalancerClient extends ServiceInstanceChooser { 
  8.  
  9.   <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException; 
  10.   <T> T execute(String serviceId, ServiceInstance serviceInstance, 
  11.          LoadBalancerRequest<T> request) throws IOException; 
  12.   // 将服务实例信息替换还具体的 IP 信息  
  13.   URI reconstructURI(ServiceInstance instance, URI original); 
  14.  

我们先来分析 RibbonLoadBalancerClient 的 choose 方法

  1. @Override 
  2. public ServiceInstance choose(String serviceId) { 
  3.    return choose(serviceId, null); 
  4.  
  5. // 通过服务名选择具体的服务实例 
  6. public ServiceInstance choose(String serviceId, Object hint) { 
  7.    Server server = getServer(getLoadBalancer(serviceId), hint); 
  8.    if (server == null) { 
  9.       return null
  10.    } 
  11.    return new RibbonServer(serviceId, server, isSecure(server, serviceId), 
  12.          serverIntrospector(serviceId).getMetadata(server)); 
  13.  
  14. // 通过服务名选择一个负载均衡器, 默认是 `ZoneAwareLoadBalancer` 
  15. protected ILoadBalancer getLoadBalancer(String serviceId) { 
  16.    return this.clientFactory.getLoadBalancer(serviceId); 
  17.  
  18. // 获取服务 
  19. protected Server getServer(ILoadBalancer loadBalancer) { 
  20.    return getServer(loadBalancer, null); 
  21. protected Server getServer(ILoadBalancer loadBalancer, Object hint) { 
  22.    if (loadBalancer == null) { 
  23.       return null
  24.    } 
  25.    // Use 'default' on a null hint, or just pass it on
  26.    return loadBalancer.chooseServer(hint != null ? hint : "default"); 

LoadBalancerInterceptor 执行的时候是直接委托执行的 loadBalancer.execute() 这个方法:

  1. // LoadBalancerRequest 是通过 LoadBalancerRequestFactory.createRequest(request, body, execution) 创建 
  2. // 它实现 LoadBalancerRequest 接口是用的一个匿名内部类,泛型类型是ClientHttpResponse 
  3. // 因为最终执行的显然还是执行器:ClientHttpRequestExecution.execute() 
  4. @Override 
  5. public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { 
  6.   return execute(serviceId, request, null); 
  7.  
  8. public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException { 
  9.   // 拿到负载均衡器,然后拿到一个serverInstance实例 
  10.   ILoadBalancer loadBalancer = getLoadBalancer(serviceId); 
  11.   Server server = getServer(loadBalancer, hint); 
  12.   if (server == null) { // 若没找到就直接抛出异常。这里使用的是IllegalStateException这个异常 
  13.     throw new IllegalStateException("No instances available for " + serviceId); 
  14.   } 
  15.  
  16.   // 把Server适配为RibbonServer  isSecure:客户端是否安全 
  17.   // serverIntrospector内省  参考配置文件:ServerIntrospectorProperties 
  18.   RibbonServer ribbonServer = new RibbonServer(serviceId, server, 
  19.       isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); 
  20.  
  21.   //调用本类的重载接口方法 
  22.   return execute(serviceId, ribbonServer, request); 
  23.  
  24. // 它的参数是 ServiceInstance --> 已经确定了唯一的Server实例 
  25. @Override 
  26. public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException { 
  27.  
  28.   // 拿到 Server,RibbonServer 是 execute 时的唯一实现 
  29.   Server server = null
  30.   if (serviceInstance instanceof RibbonServer) { 
  31.     server = ((RibbonServer) serviceInstance).getServer(); 
  32.   } 
  33.   if (server == null) { 
  34.     throw new IllegalStateException("No instances available for " + serviceId); 
  35.   } 
  36.  
  37.   // 执行的上下文是和serviceId绑定的 
  38.   RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId); 
  39.   ...  
  40.   // 真正的向server发送请求,得到返回值 
  41.   // 因为有拦截器,所以这里肯定说执行的是InterceptingRequestExecution.execute()方法 
  42.   // so会调用ServiceRequestWrapper.getURI(),从而就会调用reconstructURI()方法 
  43.     T returnVal = request.apply(serviceInstance); 
  44.     return returnVal; 
  45.   ... // 异常处理 

returnVal 是一个 ClientHttpResponse,最后交给 handleResponse()方法来处理异常情况(若存在的话),若无异常就交给提取器提值:responseExtractor.extractData(response),这样整个请求就算全部完成了。

ZoneAwareLoadBalancer

负载均衡器 ZoneAwareLoadBalancer 的类图结构如下图所示。它 DynamicServerListLoadBalancer 它的父类, 核心方法 重置和初始化:restOfInit(clientConfig) 更新服务列表:updateListOfServers(); 这个方需要调用到 ServerList.getUpdatedListOfServers() 这里就会调用到具体的注册中心实现,以 Nacos 为例他的实现就是 NacosServerList#getUpdatedListOfServers();

  • 更新所有服务列表:updateAllServerList();
  • 设置所有服务列表 setServersList() ZoneAwareLoadBalancer 它的核心方法:
  • 选择服务实例 chooseServer()
  • 选择负载均衡器 getLoadBalancer
  • 选择区域内的服务实例:zoneLoadBalancer.chooseServer

Ribbon 总结

针对 @LoadBalanced 下的 RestTemplate 的使用,我总结如下:

  • 传入的String类型的url必须是绝对路径(http://...),否则抛出异常:java.lang.IllegalArgumentException: URI is not absolute
  • serviceId 不区分大小写(http://order-service/...效果同http://OERDER-SERVICE/...)
  • serviceId 后请不要跟 port 端口号

最后,需要特别指出的是:标注有@LoadBalanced 的 RestTemplate 只能填写 serviceId 而不能再写 IP地址/域名去发送请求了, 若你的项目中两种 case 都有需要,需要定义多个 RestTemplate 分别应对不同的使用场景

Nacos 服务查询

客户端查询

如果我们使用默认的 Nacos 客户端,那么走的就是 NacosServerList#getUpdatedListOfServers();接口来查询服务列表。

  1. public class NacosServerList extends AbstractServerList<NacosServer> { 
  2.  
  3.   private NacosDiscoveryProperties discoveryProperties; 
  4.  
  5.   @Override 
  6.   public List<NacosServer> getUpdatedListOfServers() { 
  7.     return getServers(); 
  8.   } 
  9.      
  10.     private List<NacosServer> getServers() { 
  11.     try { 
  12.       String group = discoveryProperties.getGroup(); 
  13.       // discoveryProperties.namingServiceInstance()  
  14.             // 最终通过反射获取 com.alibaba.nacos.client.naming.NacosNamingService 实例 
  15.             List<Instance> instances = discoveryProperties.namingServiceInstance() 
  16.           .selectInstances(serviceId, grouptrue); 
  17.       return instancesToServerList(instances); 
  18.     } 
  19.     catch (Exception e) { 
  20.       throw new IllegalStateException( 
  21.           "Can not get service instances from nacos, serviceId=" + serviceId, 
  22.           e); 
  23.     } 
  24.   } 

然后调用 selectInstances 方法

  1. @Override 
  2. public List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy, 
  3.                                       boolean subscribe) throws NacosException { 
  4.  
  5.     ServiceInfo serviceInfo; 
  6.     // subscribe 默认传的是 true 
  7.     if (subscribe) { 
  8.         serviceInfo = hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName), 
  9.                                                  StringUtils.join(clusters, ",")); 
  10.     } else { 
  11.         serviceInfo = hostReactor 
  12.             .getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName), 
  13.                                               StringUtils.join(clusters, ",")); 
  14.     } 
  15.     return selectInstances(serviceInfo, healthy); 

其实核心的逻辑在 hostReactor.getServiceInfo 在查询服务信息里面会把当前的 serviceName、 clusters 转换为 key, 然后通过 getServiceInfo0 方法查询服务信息这里主要是查询的是本地的数据。

如果 null == serviceObj 会在 updateServiceNow 里面去调用 /instance/list接口查询服务信息

  1. public ServiceInfo getServiceInfo(final String serviceName, final String clusters) { 
  2.          
  3.         NAMING_LOGGER.debug("failover-mode: " + failoverReactor.isFailoverSwitch()); 
  4.         String key = ServiceInfo.getKey(serviceName, clusters); 
  5.         if (failoverReactor.isFailoverSwitch()) { 
  6.             return failoverReactor.getService(key); 
  7.         } 
  8.          
  9.         ServiceInfo serviceObj = getServiceInfo0(serviceName, clusters); 
  10.          
  11.         if (null == serviceObj) { 
  12.             serviceObj = new ServiceInfo(serviceName, clusters); 
  13.              
  14.             serviceInfoMap.put(serviceObj.getKey(), serviceObj); 
  15.              
  16.             updatingMap.put(serviceName, new Object()); 
  17.             updateServiceNow(serviceName, clusters); 
  18.             updatingMap.remove(serviceName); 
  19.              
  20.         } else if (updatingMap.containsKey(serviceName)) { 
  21.             // UPDATE_HOLD_INTERVAL 为常量默认金辉进去 
  22.             if (UPDATE_HOLD_INTERVAL > 0) { 
  23.                 // hold a moment waiting for update finish 
  24.                 synchronized (serviceObj) { 
  25.                     try { 
  26.                         // 最大等待时间 5s, 在更新 serviceObj 之后, 就会执行 notifyAll() 
  27.                         // 方法入口 updateService(String serviceName, String clusters) 
  28.                         // 最大延迟 2s DEFAULT_DELAY = 1 
  29.                         serviceObj.wait(UPDATE_HOLD_INTERVAL); 
  30.                     } catch (InterruptedException e) { 
  31.                         NAMING_LOGGER 
  32.                                 .error("[getServiceInfo] serviceName:" + serviceName + ", clusters:" + clusters, e); 
  33.                     } 
  34.                 } 
  35.             } 
  36.         } 
  37.          
  38.       // 通过 Schedule 更新 服务信息  
  39.         scheduleUpdateIfAbsent(serviceName, clusters); 
  40.          
  41.       // 获取最新的值 
  42.         return serviceInfoMap.get(serviceObj.getKey()); 
  43.     } 

代码看到这里我们不难理解,为什么第一次 Ribbon 调用的时候都会比较慢,因为它回去初始化服务列表,然后通过 Nacos Client 去 Nacos 查询服务实例信息。

服务端处理

服务端通过 /instance/list 接口来处理服务实例信息查询请求。首先服务实例信息都是被存储在 ConcurrentHashMap 中

  1. /** 
  2.  * Map(namespace, Map(group::serviceName, Service)). 
  3.  */ 
  4. private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>(); 

在我们查询的过程中主要是通过 ServiceManager 来进行管理, 核心的入口方法在 InstanceController#doSrvIpxt 中

  1. public ObjectNode doSrvIpxt(String namespaceId, String serviceName, String agent, String clusters, String clientIP, 
  2.           int udpPort, String env, boolean isCheck, String app, String tid, boolean healthyOnly) throws Exception { 
  3.        
  4.       ClientInfo clientInfo = new ClientInfo(agent); 
  5.       ObjectNode result = JacksonUtils.createEmptyJsonNode(); 
  6.       Service service = serviceManager.getService(namespaceId, serviceName); 
  7.       long cacheMillis = switchDomain.getDefaultCacheMillis(); 
  8.        
  9.       // now try to enable the push 
  10.       try { 
  11.           if (udpPort > 0 && pushService.canEnablePush(agent)) { 
  12.                
  13.               pushService 
  14.                       .addClient(namespaceId, serviceName, clusters, agent, new InetSocketAddress(clientIP, udpPort), 
  15.                               pushDataSource, tid, app); 
  16.               cacheMillis = switchDomain.getPushCacheMillis(serviceName); 
  17.           } 
  18.       } catch (Exception e) { 
  19.           Loggers.SRV_LOG 
  20.                   .error("[NACOS-API] failed to added push client {}, {}:{}", clientInfo, clientIP, udpPort, e); 
  21.           cacheMillis = switchDomain.getDefaultCacheMillis(); 
  22.       } 
  23.        
  24.       if (service == null) { 
  25.           if (Loggers.SRV_LOG.isDebugEnabled()) { 
  26.               Loggers.SRV_LOG.debug("no instance to serve for service: {}", serviceName); 
  27.           } 
  28.           result.put("name", serviceName); 
  29.           result.put("clusters", clusters); 
  30.           result.put("cacheMillis", cacheMillis); 
  31.           result.replace("hosts", JacksonUtils.createEmptyArrayNode()); 
  32.           return result; 
  33.       } 
  34.        
  35.       checkIfDisabled(service); 
  36.        
  37.       List<Instance> srvedIPs; 
  38.        
  39.       // 查询所有的服务 
  40.       // 内部会更新服务列表 
  41.       // allInstances.addAll(persistentInstances); 
  42.       // allInstances.addAll(ephemeralInstances); 
  43.       srvedIPs = service.srvIPs(Arrays.asList(StringUtils.split(clusters, ","))); 
  44.        
  45.       // filter ips using selector: 
  46.       if (service.getSelector() != null && StringUtils.isNotBlank(clientIP)) { 
  47.           srvedIPs = service.getSelector().select(clientIP, srvedIPs); 
  48.       } 
  49.        
  50.       if (CollectionUtils.isEmpty(srvedIPs)) { 
  51.            
  52.           if (Loggers.SRV_LOG.isDebugEnabled()) { 
  53.               Loggers.SRV_LOG.debug("no instance to serve for service: {}", serviceName); 
  54.           } 
  55.            
  56.           if (clientInfo.type == ClientInfo.ClientType.JAVA 
  57.                   && clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) { 
  58.               result.put("dom", serviceName); 
  59.           } else { 
  60.               result.put("dom", NamingUtils.getServiceName(serviceName)); 
  61.           } 
  62.            
  63.           result.put("name", serviceName); 
  64.           result.put("cacheMillis", cacheMillis); 
  65.           result.put("lastRefTime", System.currentTimeMillis()); 
  66.           result.put("checksum", service.getChecksum()); 
  67.           result.put("useSpecifiedURL"false); 
  68.           result.put("clusters", clusters); 
  69.           result.put("env", env); 
  70.           result.set("hosts", JacksonUtils.createEmptyArrayNode()); 
  71.           result.set("metadata", JacksonUtils.transferToJsonNode(service.getMetadata())); 
  72.           return result; 
  73.       } 
  74.        
  75.       Map<Boolean, List<Instance>> ipMap = new HashMap<>(2); 
  76.       ipMap.put(Boolean.TRUE, new ArrayList<>()); 
  77.       ipMap.put(Boolean.FALSE, new ArrayList<>()); 
  78.        
  79.       for (Instance ip : srvedIPs) { 
  80.           ipMap.get(ip.isHealthy()).add(ip); 
  81.       } 
  82.        
  83.       if (isCheck) { 
  84.           result.put("reachProtectThreshold"false); 
  85.       } 
  86.        
  87.       double threshold = service.getProtectThreshold(); 
  88.        
  89.       if ((float) ipMap.get(Boolean.TRUE).size() / srvedIPs.size() <= threshold) { 
  90.            
  91.           Loggers.SRV_LOG.warn("protect threshold reached, return all ips, service: {}", serviceName); 
  92.           if (isCheck) { 
  93.               result.put("reachProtectThreshold"true); 
  94.           } 
  95.            
  96.           ipMap.get(Boolean.TRUE).addAll(ipMap.get(Boolean.FALSE)); 
  97.           ipMap.get(Boolean.FALSE).clear(); 
  98.       } 
  99.        
  100.       if (isCheck) { 
  101.           result.put("protectThreshold", service.getProtectThreshold()); 
  102.           result.put("reachLocalSiteCallThreshold"false); 
  103.            
  104.           return JacksonUtils.createEmptyJsonNode(); 
  105.       } 
  106.        
  107.       ArrayNode hosts = JacksonUtils.createEmptyArrayNode(); 
  108.        
  109.       for (Map.Entry<Boolean, List<Instance>> entry : ipMap.entrySet()) { 
  110.           List<Instance> ips = entry.getValue(); 
  111.            
  112.           if (healthyOnly && !entry.getKey()) { 
  113.               continue
  114.           } 
  115.            
  116.           for (Instance instance : ips) { 
  117.                
  118.               // remove disabled instance: 
  119.               if (!instance.isEnabled()) { 
  120.                   continue
  121.               } 
  122.                
  123.               ObjectNode ipObj = JacksonUtils.createEmptyJsonNode(); 
  124.                
  125.               ipObj.put("ip", instance.getIp()); 
  126.               ipObj.put("port", instance.getPort()); 
  127.               // deprecated since nacos 1.0.0: 
  128.               ipObj.put("valid", entry.getKey()); 
  129.               ipObj.put("healthy", entry.getKey()); 
  130.               ipObj.put("marked", instance.isMarked()); 
  131.               ipObj.put("instanceId", instance.getInstanceId()); 
  132.               ipObj.set("metadata", JacksonUtils.transferToJsonNode(instance.getMetadata())); 
  133.               ipObj.put("enabled", instance.isEnabled()); 
  134.               ipObj.put("weight", instance.getWeight()); 
  135.               ipObj.put("clusterName", instance.getClusterName()); 
  136.               if (clientInfo.type == ClientInfo.ClientType.JAVA 
  137.                       && clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) { 
  138.                   ipObj.put("serviceName", instance.getServiceName()); 
  139.               } else { 
  140.                   ipObj.put("serviceName", NamingUtils.getServiceName(instance.getServiceName())); 
  141.               } 
  142.                
  143.               ipObj.put("ephemeral", instance.isEphemeral()); 
  144.               hosts.add(ipObj); 
  145.                
  146.           } 
  147.       } 
  148.        
  149.       result.replace("hosts", hosts); 
  150.       if (clientInfo.type == ClientInfo.ClientType.JAVA 
  151.               && clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) { 
  152.           result.put("dom", serviceName); 
  153.       } else { 
  154.           result.put("dom", NamingUtils.getServiceName(serviceName)); 
  155.       } 
  156.       result.put("name", serviceName); 
  157.       result.put("cacheMillis", cacheMillis); 
  158.       result.put("lastRefTime", System.currentTimeMillis()); 
  159.       result.put("checksum", service.getChecksum()); 
  160.       result.put("useSpecifiedURL"false); 
  161.       result.put("clusters", clusters); 
  162.       result.put("env", env); 
  163.       result.replace("metadata", JacksonUtils.transferToJsonNode(service.getMetadata())); 
  164.       return result; 
  165.   } 

在上面的核心逻辑主要是:

  • 调用 service.srvIPs 方法查询所有的服务实例信息
  • Cluster#allIPs会将所有的服务注册信息写到服务注册列表。

参考链接

https://nacos.io

https://zhuanlan.zhihu.com

https://blog.csdn.net/f641385712/article/details/100788040

 

责任编辑:武晓燕 来源: 运维开发故事
相关推荐

2022-03-10 07:41:36

调用服务Nacos

2009-10-09 17:18:13

RHEL配置NIS

2009-12-11 13:59:35

F#

2010-03-02 14:06:37

WCF服务实例管理模式

2009-08-14 17:04:19

Windows后台服务

2010-02-26 14:49:10

WCF服务实例单一性

2010-09-24 19:12:11

SQL隐性事务

2013-03-19 10:35:24

Oracle

2013-02-26 10:23:52

F5电信

2022-07-08 08:37:23

Nacos服务注册动态配置

2017-10-21 21:46:32

2023-04-10 23:05:54

NacosOpenFeignRibbon

2017-09-05 14:05:11

微服务spring clou路由

2021-09-06 06:45:07

NacosUdp通信

2021-07-12 08:00:21

Nacos 服务注册源码分析

2020-10-09 18:41:55

AWS云服务云计算

2020-10-13 14:03:50

搭建ngrok服务

2017-08-09 15:50:47

Spring Clou微服务架构

2022-06-08 10:58:00

服务配置Nacos

2010-10-28 16:42:04

oracle多表查询
点赞
收藏

51CTO技术栈公众号