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


    本文收录于专栏 Nacos 中 。


    前言

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

    确定前端路由

    我们已经向Nacos中注册了一个服务,现在去前端确定查询的路由是什么
    在这里插入图片描述
    确定前端请求路由:/nacos/v1/ns/catalog/services
    通过路由确定后端代码位置:

    package com.alibaba.nacos.naming.controllers;
    CatalogController.listDetail()
    
    • 1
    • 2

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    查看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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    ServiceManager.getInstance()这里一看就是一个经典的单例写法,那我们接下来把精力放到getSingletons这个方法上。
    namespaceId默认是public

    ServiceManager

    public Set<Service> getSingletons(String namespace) {
        return namespaceSingletonMaps.getOrDefault(namespace, new HashSet<>(1));
    }
    
    • 1
    • 2
    • 3

    通过代码我们发现,获取制定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;
    	
    	//...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    我们可以发现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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    观察代码我们发现,往map中写数据的只有这一个方法,那么这个方法是在什么时机被调用的呢?
    我们重新梳理之前客户端注册的部分逻辑:

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

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

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

    总结

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

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

  • 相关阅读:
    MMDP: A Mobile-IoT Based Multi-Modal Reinforcement Learning Service Framework
    【学习笔记21】JavaScript数组的基本方法
    数据库表的实时同步更新
    户外led显示屏中的裸眼3D效果是怎么做出来的?
    Postman如何做接口测试,那些不得不知道的技巧
    CanOpen协议的伺服驱动控制
    Android Studio 创建项目不自动生成BuildConfig文件
    WPF中获取TreeView以及ListView获取其本身滚动条进行滚动
    【MATLAB源码-第67期】基于麻雀搜索算法(SSA)的无人机三维地图路径规划,输出最短路径和适应度曲线。
    iOS基础小结(一)
  • 原文地址:https://blog.csdn.net/Paranoia_ZK/article/details/133498440