一,nacos的服务发现
上一篇主要是讲解了这个nacos的服务注册,接下来主要讲讲这个nacos的服务发现。
1,nacos服务发现概述
就是说每个客户端都会去这个服务注册中心里面拉取这个注册表里面的全部实例,最后将这个实例存储在这个客户端的本地缓存里面。
2,nacos服务发现源码分析
2.1,客户端源码分析
在这个NacosNamingService类里面,主要有一个方法getAllInstances方法,主要是获取注册表里里面的所有的实例的信息。最后通过一个list集合将这个全部的实例信息返回。
@Override public List<Instance> getAllInstances(String sn, String gn, List<String> clusters,boolean subscribe) throws NacosException { ServiceInfo serviceInfo; if (subscribe) { //获取服务信息 serviceInfo = hostReactor.getServiceInfo( NamingUtils.getGroupedName(sn, gn),StringUtils.join(clusters, ",")); }else { serviceInfo = hostReactor.getServiceInfoDirectlyFromServer( NamingUtils.getGroupedName(sn,gn),StringUtils.join(clusters,",")); } List<Instance> list; if (serviceInfo == null || CollectionUtils.isEmpty(list = serviceInfo.getHosts())) { return new ArrayList<Instance>(); } return list; }
客户端主要通过这个getServiceInfo方法来查看实例的信息,在这个方法中,主要有以下的代码。首先会判断是否第一次拉取注册中心的全部实例,如果是的话就会去拉取这个注册中心里面的全部数据。然后的话客户端会开启一个定时任务,每隔几秒就会去拉取一次这个注册中心里面的全部实例,然后存放在本地的缓存里面。
public ServiceInfo getServiceInfo(final String serviceName, final String clusters){ //获取服务的信息 ServiceInfo serviceObj = getServiceInfo0(serviceName, clusters); //客户端第一次获取这个注册表信息为空 if (null == serviceObj) { serviceObj = new ServiceInfo(serviceName, clusters); serviceInfoMap.put(serviceObj.getKey(), serviceObj); updatingMap.put(serviceName, new Object()); //会去拉取这个注册中心里面的注册表信息 updateServiceNow(serviceName, clusters); updatingMap.remove(serviceName); } //如果本地缓存里面已有这个注册表信息 else if (updatingMap.containsKey(serviceName)) { if (UPDATE_HOLD_INTERVAL > 0) { synchronized (serviceObj) { try { serviceObj.wait(UPDATE_HOLD_INTERVAL); } catch (InterruptedException e) { } } } } //客户端会开启一个定时任务,每隔几秒会去拉取注册中心里面的全部实例的信息 scheduleUpdateIfAbsent(serviceName, clusters); return serviceInfoMap.get(serviceObj.getKey()); }
第一次拉取这个注册中心里面的全部实例主要通过这个updateServiceNow方法实现,里面有一个queryList方法,如下。最终会通过这个reqApi的这个方法,向这个服务的注册中心发起http请求建立连接,最后会拉取服务注册中心里面的全部的这个实例的信息。
public String queryList(String serviceName, String clusters, int udpPort, boolean healthyOnly) throws NacosException { final Map<String, String> params = new HashMap<String, String>(8); params.put(CommonParams.NAMESPACE_ID, namespaceId); params.put(CommonParams.SERVICE_NAME, serviceName); params.put("clusters", clusters); params.put("udpPort", String.valueOf(udpPort)); params.put("clientIP", NetUtils.localIP()); params.put("healthyOnly", String.valueOf(healthyOnly)); //获取服务注册中心里面的全部实例 return reqApi(UtilAndComs.nacosUrlBase + "/instance/list", params, HttpMethod.GET); }
2.2,服务端中获取全部实例
由于整个nacos是一个web的服务应用,因此可以在这个instanceContoller类里面查看到这个客户端获取全部服务端里面的实例的请求。这个需要下载nacos的源码,主要是使用nacos的1.4.1的版本。在这个list方法里面,有一个doSrvIpxt方法
@GetMapping("/list") @Secured(parser = NamingResourceParser.class, action = ActionTypes.READ) public ObjectNode list(HttpServletRequest request) throws Exception { String namespaceId = WebUtils.optional(request,CommonParams.NAMESPACE_ID,Constants.DEFAULT_NAMESPACE_ID); String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME); NamingUtils.checkServiceNameFormat(serviceName); String agent = WebUtils.getUserAgent(request); String clusters = WebUtils.optional(request, "clusters", StringUtils.EMPTY); String clientIP = WebUtils.optional(request, "clientIP", StringUtils.EMPTY); int udpPort = Integer.parseInt(WebUtils.optional(request, "udpPort", "0")); String env = WebUtils.optional(request, "env", StringUtils.EMPTY); boolean isCheck = Boolean.parseBoolean(WebUtils.optional(request, "isCheck", "false")); String app = WebUtils.optional(request, "app", StringUtils.EMPTY); String tenant = WebUtils.optional(request, "tid", StringUtils.EMPTY); boolean healthyOnly = Boolean.parseBoolean(WebUtils.optional(request, "healthyOnly", "false")); return doSrvIpxt(namespaceId, serviceName, agent, clusters, clientIP, udpPort, env, isCheck, app, tenant, healthyOnly); }
在这个doSrvIpxt方法里面,会有一个获取所有服务的实例
Service service = serviceManager.getService(namespaceId, serviceName); //获取所有服务的实例 srvedIPs = service.srvIPs(Arrays.asList(StringUtils.split(clusters, ",")));
然后通过这个srvIPs方法获取所有的实例
public List<Instance> srvIPs(List<String> clusters) { if (CollectionUtils.isEmpty(clusters)) { clusters = new ArrayList<>(); clusters.addAll(clusterMap.keySet()); } return allIPs(clusters); }
最后调用这个allIPs方法来对所有实例进行一个获取
public List<Instance> allIPs(List<String> clusters) { List<Instance> result = new ArrayList<>(); for (String cluster : clusters) { Cluster clusterObj = clusterMap.get(cluster); if (clusterObj == null) { continue; } //将所有的实例全部添加 result.addAll(clusterObj.allIPs()); } //将获取的实例返回 return result; }
全部的实例通过这个addAll方法将实例全部添加,里面包括了这个持久化实例和临时实例。这样的话就成功获取到了服务端的全部实例
public List<Instance> allIPs() { List<Instance> allInstances = new ArrayList<>(); //持久化实例,对应的ap架构 allInstances.addAll(persistentInstances); //临时实例,对应的ap架构 allInstances.addAll(ephemeralInstances); return allInstances; }
二,总结
在进行服务注册之后,该服务就会去这个服务注册中心里面拉取全部的微服务实例,会将全部的实例存在本地的缓存里面,并且同时会去开启一个定时任务,每隔几秒就会去拉取一次最新的微服务实例。