Nacos跨Group及Namespace发现服务

背景

设计小公司内部微服务框架,提供一套公共的基础服务给所有业务系统使用(如网关服务、认证服务、系统服务、问价服务、流程服务等)。如果全注册在同一Nacos的单一Namespace或group下会比较混乱;每个系统单独部署一套的话又比较麻烦,权衡决定采用同一个Nacos,公共服务注册在public namespace下,其他系统每个系统单分一个命名空间出来,但是可以发现public下的公共服务并进行feign调用。

网上搜这方面资料乱七八糟的,要不就是CSDN恬不知耻收费,故做一个整合,方便自己以及网友浏览查阅。

环境

复制代码
SpringBoot 3.5.0
JDK 17
Nacos 2.2.0

跨Group服务发现

正常来说,Nacos的服务只有在本Namespace + 本Group下才能互相发现的,就是有两层隔离,我们先了解下如何突破Group的限制,有些场景你可能需要。

直接上代码(添加在SpringBoot服务中或项目common模块):

Java 复制代码
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.utils.NamingUtils;
import org.springframework.cloud.client.ServiceInstance;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 重写nacos服务发现逻辑,使支持跨group发现服务
 */
public class CustomNacosServiceDiscovery extends NacosServiceDiscovery {

    private final NacosDiscoveryProperties discoveryProperties;

    private final NacosServiceManager nacosServiceManager;

    private final List<String> groups;

    public CustomNacosServiceDiscovery(NacosDiscoveryProperties discoveryProperties, NacosServiceManager nacosServiceManager) {
        super(discoveryProperties, nacosServiceManager);
        this.discoveryProperties = discoveryProperties;
        this.nacosServiceManager = nacosServiceManager;
        // 服务发现顺序:本组服务->公共分组(DEFAULT_GROUP)服务->其他组服务
        this.groups = new ArrayList<>(Arrays.asList(discoveryProperties.getGroup(), "DEFAULT_GROUP"))
                .stream().distinct().collect(Collectors.toList());
    }

    /**
     * Return the names of all services.
     *
     * @return list of service names
     * @throws NacosException nacosException
     */
    @Override
    public List<String> getServices() throws NacosException {
        Set<String> services = new LinkedHashSet<>();
        NamingService nameService = this.discoveryProperties.namingServiceInstance();
        for (String group : groups) {
            services.addAll(nameService.getServicesOfServer(1, Integer.MAX_VALUE, group).getData());
        }
        return new ArrayList<>(services);
    }

    /**
     * 重写服务发现逻辑
     */
    @Override
    public List<ServiceInstance> getInstances(String serviceId) throws NacosException {
        NamingService nameService = this.discoveryProperties.namingServiceInstance();
        // 优先发现同组服务
        List<Instance> instances = new ArrayList<>();
        // 在DEFAULT_GROUP中发现公共服务(同组服务已经注册的,使用同组服务而非公共服务)
        for (String group : groups) { // 按照GROUPS内的顺序依次发现各组服务,优先发现靠前组的服务,如果某服务在之前分组中出现过,不会重复注册
            instances.addAll(
                    nameService.selectInstances(serviceId, group, true)
                            .stream()
                            .filter(e -> !instances.stream().map(Instance::getServiceName).collect(Collectors.toList())
                                    .contains(NamingUtils.getServiceName(e.getServiceName())))
                            .collect(Collectors.toList()));
        }
        return hostToServiceInstanceList(instances, serviceId);
    }

}

核心是继承NacosServiceDiscovery这个类,重写getServicesgetInstances(String serviceId)这俩个方法,前者用于返回所有可用的服务的名称,后者则根据服务名称来获得详细的服务实例信息。

上面的逻辑我们使用DEFAULT_GROUP作为可被公共发现的Group,并写死到代码中,实际操作你可以根据自己需求来更改逻辑或是改为yml配置,可以更灵活一些。

添加完这个类后,我们再写一个配置类来将该Bean启用,覆盖默认逻辑:

Java 复制代码
import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration;
import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Nacos自动配置类,加载自定义容器
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
@AutoConfigureBefore({NacosDiscoveryAutoConfiguration.class})
public class CustomNacosDiscoveryAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public NacosServiceDiscovery nacosServiceDiscovery(NacosDiscoveryProperties nacosDiscoveryProperties, NacosServiceManager nacosServiceManager) {
       return new CustomNacosServiceDiscovery(nacosDiscoveryProperties, nacosServiceManager);
    }

}

跨Namespace服务发现

这里在上面的基础上,进一步修改自定义的NacosServiceDiscovery类的逻辑:

Java 复制代码
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.utils.NamingUtils;
import org.springframework.cloud.client.ServiceInstance;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 重写nacos服务发现逻辑,使支持跨Namespace发现服务
 */
public class CustomNacosServiceDiscovery extends NacosServiceDiscovery {

    private final NacosDiscoveryProperties discoveryProperties;

    private final NacosServiceManager nacosServiceManager;

    private NamingService publicNamingService;

    private final List<String> groups;

    public CustomNacosServiceDiscovery(NacosDiscoveryProperties discoveryProperties, NacosServiceManager nacosServiceManager) {
       super(discoveryProperties, nacosServiceManager);
       this.discoveryProperties = discoveryProperties;
       this.nacosServiceManager = nacosServiceManager;
       initPublicNamingService();
       // 服务发现顺序:本组服务->公共分组(DEFAULT_GROUP)服务->其他组服务
       this.groups = new ArrayList<>(Arrays.asList(discoveryProperties.getGroup(), "DEFAULT_GROUP"))
          .stream().distinct().collect(Collectors.toList());
    }

    /**
     * 创建public命名空间的NamingService实例
     */
    private void initPublicNamingService() {
       try {
          Properties props = new Properties();
          // 复用默认配置中的服务器地址、鉴权等信息
          props.setProperty("serverAddr", discoveryProperties.getServerAddr());
          props.setProperty("username", discoveryProperties.getUsername());
          props.setProperty("password", discoveryProperties.getPassword());
          // 命名空间固定设置为 "public"
          props.setProperty("namespace", "public");
          this.publicNamingService = NacosFactory.createNamingService(props);
       } catch (NacosException e) {
          throw new RuntimeException("Failed to create NamingService for public namespace", e);
       }
    }

    /**
     * Return the names of all services.
     *
     * @return list of service names
     * @throws NacosException nacosException
     */
    @Override
    public List<String> getServices() throws NacosException {
       Set<String> services = new LinkedHashSet<>();
       // step1:发现本命名空间内所有GROUP的服务
       NamingService nameService = this.discoveryProperties.namingServiceInstance();
       for (String group : groups) {
          services.addAll(nameService.getServicesOfServer(1, Integer.MAX_VALUE, group).getData());
       }
       // step2:发现public命名空间下的DEFAULT_GROUP的服务
       services.addAll(publicNamingService.getServicesOfServer(1, Integer.MAX_VALUE).getData());
       return new ArrayList<>(services);
    }

    /**
     * 重写服务发现逻辑
     */
    @Override
    public List<ServiceInstance> getInstances(String serviceId) throws NacosException {
       NamingService nameService = this.discoveryProperties.namingServiceInstance();
       // step1:发现本命名空间内所有GROUP的服务
       List<Instance> instances = new ArrayList<>();
       for (String group : groups) { // 按照GROUPS内的顺序依次发现各组服务,优先发现靠前组的服务,如果某服务在之前分组中出现过,不会重复注册
          instances.addAll(
             nameService.selectInstances(serviceId, group, true)
                .stream()
                .filter(e -> !instances.stream().map(Instance::getServiceName).collect(Collectors.toList())
                   .contains(NamingUtils.getServiceName(e.getServiceName())))
                .collect(Collectors.toList()));
       }
       // step2:发现public命名空间下的DEFAULT_GROUP的服务
       instances.addAll(publicNamingService.selectInstances(serviceId, true));
       return hostToServiceInstanceList(instances, serviceId);
    }

}

与之前不同的是,多出来一个NamingService对象,本来我们使用:

Java 复制代码
this.discoveryProperties.namingServiceInstance()

这个方法拿到的是对应yml中配置的那个命名空间,假如我们现在是一个业务系统,那此时拿到的就是私有namespace的NamingService对象,现在需要获取public这个命名空间下的服务,所以需要一个能代表public的NamingService,这里我查阅源码后参考其方法写了一个initPublicNamingService方法来初始化这个对象。

其他有的博客里可能有提到再新建一个discoveryProperties继承原有的,然后通过这个新的discoveryProperties来新建出NamingService, 这个应该是最标准的流程,我们这里取巧简化一下。

总结

代码就这么多,也不是很复杂,但是查起来是真费劲,这也是中文互联网现在的一个现状吧,希望能抛砖引玉,方便一下同行。转载望标明出处。

相关推荐
沐浴露z4 小时前
【JVM】详解 对象的创建
java·jvm
weixin_445476684 小时前
Java并发编程——提前聊一聊CompletableFuture和相关业务场景
java·并发·异步
ChinaRainbowSea4 小时前
11. Spring AI + ELT
java·人工智能·后端·spring·ai编程
不会写DN4 小时前
用户头像文件存储功能是如何实现的?
java·linux·后端·golang·node.js·github
聪明的笨猪猪4 小时前
Java JVM “类加载与虚拟机执行” 面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
盖世英雄酱581364 小时前
FullGC排查,居然是它!
java·后端
老K的Java兵器库4 小时前
集合性能基准测试报告:ArrayList vs LinkedList、HashMap vs TreeMap、并发 Map 四兄弟
java·开发语言
Knight_AL4 小时前
如何解决 Jacob 与 Tomcat 类加载问题:深入分析 Tomcat 类加载机制与 JVM 双亲委派机制
java·jvm·tomcat