本文收录于专栏 Nacos 中 。
文章目录
- 前言
- 确定前端路由
- CatalogController.listDetail()
- ServiceManager
- 总结
前言
前文我们分析了Nacos
中客户端注册时数据分发
的设计链路,本文根据Nacos
前端页面请求,看下前端页面中的服务列表的数据源于哪里。
确定前端路由
我们已经向Nacos
中注册了一个服务,现在去前端确定查询的路由是什么
确定前端请求路由:/nacos/v1/ns/catalog/services
通过路由确定后端代码位置:
package com.alibaba.nacos.naming.controllers;
CatalogController.listDetail()
CatalogController.listDetail()
/*** List service detail information.** @param withInstances whether return instances* @param namespaceId namespace id* @param pageNo number of page* @param pageSize size of each page* @param serviceName service name* @param groupName group name* @param containedInstance instance name pattern which will be contained in detail* @param hasIpCount whether filter services with empty instance* @return list service detail*/
@Secured(action = ActionTypes.READ)
@GetMapping("/services")
public Object listDetail(@RequestParam(required = false) boolean withInstances,@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId,@RequestParam(required = false) int pageNo, @RequestParam(required = false) int pageSize,@RequestParam(name = "serviceNameParam", defaultValue = StringUtils.EMPTY) String serviceName,@RequestParam(name = "groupNameParam", defaultValue = StringUtils.EMPTY) String groupName,@RequestParam(name = "instance", defaultValue = StringUtils.EMPTY) String containedInstance,@RequestParam(required = false) boolean hasIpCount) throws NacosException {//前端withInstances传的是false,不走这个分支if (withInstances) {return judgeCatalogService().pageListServiceDetail(namespaceId, groupName, serviceName, pageNo, pageSize);}//确定是走的这里获取的服务列表return judgeCatalogService().pageListService(namespaceId, groupName, serviceName, pageNo, pageSize, containedInstance, hasIpCount);
}
查看judgeCatalogService().pageListService(namespaceId, groupName, serviceName, pageNo, pageSize, containedInstance, hasIpCount);
的实现:
@Override
public Object pageListService(String namespaceId, String groupName, String serviceName, int pageNo, int pageSize,String instancePattern, boolean ignoreEmptyService) throws NacosException {ObjectNode result = JacksonUtils.createEmptyJsonNode();List<ServiceView> serviceViews = new LinkedList<>();//获取服务列表Collection<Service> services = patternServices(namespaceId, groupName, serviceName);if (ignoreEmptyService) {services = services.stream().filter(each -> 0 != serviceStorage.getData(each).ipCount()).collect(Collectors.toList());}result.put(FieldsConstants.COUNT, services.size());services = doPage(services, pageNo - 1, pageSize);for (Service each : services) {ServiceMetadata serviceMetadata = metadataManager.getServiceMetadata(each).orElseGet(ServiceMetadata::new);ServiceView serviceView = new ServiceView();serviceView.setName(each.getName());serviceView.setGroupName(each.getGroup());serviceView.setClusterCount(serviceStorage.getClusters(each).size());serviceView.setIpCount(serviceStorage.getData(each).ipCount());serviceView.setHealthyInstanceCount(countHealthyInstance(serviceStorage.getData(each)));serviceView.setTriggerFlag(isProtectThreshold(serviceView, serviceMetadata) ? "true" : "false");serviceViews.add(serviceView);}result.set(FieldsConstants.SERVICE_LIST, JacksonUtils.transferToJsonNode(serviceViews));return result;
}private Collection<Service> patternServices(String namespaceId, String group, String serviceName) {boolean noFilter = StringUtils.isBlank(serviceName) && StringUtils.isBlank(group);if (noFilter) {//我们前端默认传的这两个参数都是空,所以会走这里的逻辑return ServiceManager.getInstance().getSingletons(namespaceId);}Collection<Service> result = new LinkedList<>();StringJoiner regex = new StringJoiner(Constants.SERVICE_INFO_SPLITER);regex.add(getRegexString(group));regex.add(getRegexString(serviceName));String regexString = regex.toString();for (Service each : ServiceManager.getInstance().getSingletons(namespaceId)) {if (each.getGroupedServiceName().matches(regexString)) {result.add(each);}}return result;
}
ServiceManager.getInstance()
这里一看就是一个经典的单例写法,那我们接下来把精力放到getSingletons
这个方法上。
namespaceId
默认是public
。
ServiceManager
public Set<Service> getSingletons(String namespace) {return namespaceSingletonMaps.getOrDefault(namespace, new HashSet<>(1));
}
通过代码我们发现,获取制定namespace
下的服务是从一个map中获取的。
/*** Nacos service manager for v2.** @author xiweng.yy*/
public class ServiceManager {private static final ServiceManager INSTANCE = new ServiceManager();private final ConcurrentHashMap<Service, Service> singletonRepository;private final ConcurrentHashMap<String, Set<Service>> namespaceSingletonMaps;//...
}
我们可以发现ServiceManager
这个类是一个单例模式的实现,其中维护了两个map,其中一个namespaceSingletonMaps
用于存放制定namespace
下的服务,那么这个map中的数据是在什么时机存放进去的呢?
/**1. Get singleton service. Put to manager if no singleton.2. 3. @param service new service4. @return if service is exist, return exist service, otherwise return new service*/
public Service getSingleton(Service service) {singletonRepository.computeIfAbsent(service, key -> {NotifyCenter.publishEvent(new MetadataEvent.ServiceMetadataEvent(service, false));return service;});Service result = singletonRepository.get(service);namespaceSingletonMaps.computeIfAbsent(result.getNamespace(), namespace -> new ConcurrentHashSet<>());namespaceSingletonMaps.get(result.getNamespace()).add(result);return result;
}
观察代码我们发现,往map中写数据的只有这一个方法,那么这个方法是在什么时机被调用的呢?
我们重新梳理之前客户端注册的部分逻辑:
InstanceRequestHandler
接收所有实例注册、注销相关的请求InstanceRequestHandler
处理注册请求时,会调用EphemeralClientOperationServiceImpl
中的registerInstance
方法registerInstance
方法中除了我们之前讲的发布客户端服务注册事件ClientOperationEvent.ClientRegisterServiceEvent
之外,还会往ServiceManager
中的map添加数据
registerInstance
方法对ServiceManager
的处理逻辑如下:
Service singleton = ServiceManager.getInstance().getSingleton(service);
总结
通过以上梳理,我们知道了前端服务列表
中获取的数据是源于ServiceManager
类中一个map的缓存,缓存中的数据是在客户端服务注册时添加进去的。
先梳理脉络,然后以点到面,一切都会逐渐清晰。