8、Nacos服务注册服务端源码分析(七)

本文收录于专栏 Nacos 中 。

文章目录

  • 前言
  • 确定前端路由
  • CatalogController.listDetail()
  • ServiceManager
  • 总结


前言

前文我们分析了Nacos中客户端注册时数据分发的设计链路,本文根据Nacos前端页面请求,看下前端页面中的服务列表的数据源于哪里。

确定前端路由

我们已经向Nacos中注册了一个服务,现在去前端确定查询的路由是什么
8、Nacos服务注册服务端源码分析(七)_第1张图片
确定前端请求路由:/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 service
 4. @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中写数据的只有这一个方法,那么这个方法是在什么时机被调用的呢?
我们重新梳理之前客户端注册的部分逻辑:

  1. InstanceRequestHandler接收所有实例注册、注销相关的请求
  2. InstanceRequestHandler处理注册请求时,会调用EphemeralClientOperationServiceImpl中的registerInstance方法
  3. registerInstance方法中除了我们之前讲的发布客户端服务注册事件ClientOperationEvent.ClientRegisterServiceEvent之外,还会往ServiceManager中的map添加数据

registerInstance方法对ServiceManager的处理逻辑如下:

Service singleton = ServiceManager.getInstance().getSingleton(service);

总结

通过以上梳理,我们知道了前端服务列表中获取的数据是源于ServiceManager类中一个map的缓存,缓存中的数据是在客户端服务注册时添加进去的。

先梳理脉络,然后以点到面,一切都会逐渐清晰。

你可能感兴趣的:(Nacos,java,nacos)