关于springcloud:聊聊springcloud项目同时存在多个注册中心客户端采坑记

前言

前段时间业务部门有这么一个业务场景,他们本人微服务注册核心是用eureka,他们有一些服务接口要调用兄弟部门的接口,他们定了一个服务调用计划,业务部门间接把他们服务注册到兄弟部门的注册核心,而后走rpc调用,兄弟部门注册核心是用nacos。

一开始业务部门研发间接在在pom.xml这么引入

    <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

而后我的项目启动报了如下错

Field registration in org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration$ServiceRegistryEndpointConfiguration required a single bean, but 2 were found:
    - nacosRegistration: defined by method 'nacosRegistration' in class path resource [com/alibaba/cloud/nacos/NacosDiscoveryAutoConfiguration.class]
    - eurekaRegistration: defined in BeanDefinition defined in class path resource [org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration$RefreshableEurekaClientConfiguration.class]

业务部门是这么解决的,每次发版时,如果是要纳入兄弟部门的微服务,他们就先手动正文掉eureka的客户端依赖。

起初业务部门就向咱们部门提了一个需要,pom引入多个注册核心客户端,我的项目也要能失常启动

需要剖析

从我的项目异样剖析

Field registration in org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration$ServiceRegistryEndpointConfiguration required a single bean, but 2 were found:
    - nacosRegistration: defined by method 'nacosRegistration' in class path resource [com/alibaba/cloud/nacos/NacosDiscoveryAutoConfiguration.class]
    - eurekaRegistration: defined in BeanDefinition defined in class path resource [org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration$RefreshableEurekaClientConfiguration.class]

能够看出springcloud默认的服务注册是只反对单服务注册核心。因而咱们解决的计划要么扩大springcloud源码,让它反对多注册核心,要么就是通知springcloud当存在多种注册核心客户端时,抉择一种咱们想要的注册核心客户端

本文就选实现绝对容易的计划,当存在多种注册核心客户端时,咱们通知springcloud,咱们想选的注册核心

实现计划

目前根本只有和springboot集成的开源我的项目,能够说大部分应用了主动拆卸,因而咱们的解决思路也是从主动拆卸搞起

前置常识

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
            AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }
    private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
        long startTime = System.nanoTime();
        String[] candidates = StringUtils.toStringArray(configurations);
        boolean[] skip = new boolean[candidates.length];
        boolean skipped = false;
        for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
            invokeAwareMethods(filter);
            boolean[] match = filter.match(candidates, autoConfigurationMetadata);
            for (int i = 0; i < match.length; i++) {
                if (!match[i]) {
                    skip[i] = true;
                    candidates[i] = null;
                    skipped = true;
                }
            }
        }
        if (!skipped) {
            return configurations;
        }
        List<String> result = new ArrayList<>(candidates.length);
        for (int i = 0; i < candidates.length; i++) {
            if (!skip[i]) {
                result.add(candidates[i]);
            }
        }
        if (logger.isTraceEnabled()) {
            int numberFiltered = configurations.size() - result.size();
            logger.trace("Filtered " + numberFiltered + " auto configuration class in "
                    + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
        }
        return new ArrayList<>(result);
    }

这两个代码片段,一个是主动拆卸的代码片段,一个是过滤候选哪些不须要进行主动拆卸

计划一:利用AutoConfigurationImportFilter + 自定义标识

实现的原理: 当自定的标识为nacos,通过AutoConfigurationImportFilter排除eureka的主动拆卸;反之排除nacos的主动拆卸

1、外围实现

public class RegistrationCenterAutoConfigurationImportFilter implements AutoConfigurationImportFilter, EnvironmentAware {

    private Environment environment;

    /**
     * 因为springboot主动拆卸,默认会把spring.factories的配置的类先全副加载到候选汇合中,
     * 因而当咱们代码配置启用nacos,则需把其余注册核心,比方eureka先从候选汇合排除
     * @param autoConfigurationClasses
     * @param autoConfigurationMetadata
     * @return
     */
    @Override
    public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
        Set<String> activeProfiles = Arrays.stream(environment.getActiveProfiles()).collect(Collectors.toSet());

        if(activeProfiles.contains(RegistrationCenterConstants.ACTIVE_PROFILE_NACOS)){
            return excludeMissMatchRegistrationAutoConfiguration(autoConfigurationClasses, new String[]{RegistrationCenterConstants.EUREKA_AUTO_CONFIGURATION_CLASSES});

        }else if (activeProfiles.contains(RegistrationCenterConstants.ACTIVE_PROFILE_EUREKA)){
            return excludeMissMatchRegistrationAutoConfiguration(autoConfigurationClasses, new String[]{RegistrationCenterConstants.NACOS_DISCOVERY_AUTO_CONFIGURATION_CLASSES,RegistrationCenterConstants.NACOS_DISCOVERY_CLIENT_AUTO_CONFIGURATION_CLASSES,RegistrationCenterConstants.NACOS_DISCOVERY_ENDPOINT_AUTO_CONFIGURATION_CLASSES});
        }


        return new boolean[0];
    }

    private boolean[] excludeMissMatchRegistrationAutoConfiguration(String[] autoConfigurationClasses,String[] needExcludeRegistrationAutoConfigurationClasse) {
        Map<String,Boolean> needExcludeRegistrationAutoConfigurationClzMap = initNeedExcludeRegistrationAutoConfigurationClzMap(needExcludeRegistrationAutoConfigurationClasse);
        boolean[] match = new boolean[autoConfigurationClasses.length];
        for (int i = 0; i < autoConfigurationClasses.length; i++) {
            String autoConfigurationClz = autoConfigurationClasses[i];
            match[i] = !needExcludeRegistrationAutoConfigurationClzMap.containsKey(autoConfigurationClz);

        }

        return match;
    }

    private Map<String,Boolean> initNeedExcludeRegistrationAutoConfigurationClzMap(String[] needExcludeRegistrationAutoConfigurationClasse){
        Map<String,Boolean> needExcludeRegistrationAutoConfigurationClzMap = new HashMap<>();
        for (String needExcludeRegistrationAutoConfigurationClz : needExcludeRegistrationAutoConfigurationClasse) {
            needExcludeRegistrationAutoConfigurationClzMap.put(needExcludeRegistrationAutoConfigurationClz,false);
        }

        return needExcludeRegistrationAutoConfigurationClzMap;

    }

    @Override
    public void setEnvironment(Environment environment) {
         this.environment = environment;
    }


}

2、配置spring.factories

org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
com.github.lybgeek.registration.autoconfigure.filter.RegistrationCenterAutoConfigurationImportFilter

计划二:利用application-${指定注册核心标识} + spring.profiles.active

1、在要激活的注册核心的文件禁用其余注册核心客户端

比方appliication-nacos.yml禁用eureka

spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

# 禁用eureka客户端主动注册
eureka:
  client:
    enabled: false

2、激活想要注册的注册核心

spring:
  application:
    name: springboot-registration-client
  profiles:
    active: nacos

总结

这两种计划集体是比拟举荐计划二,因为改变最小。计划一比拟实用于没有提供是否须要激活注册核心开关的注册核心。其次如果咱们要排除某些开源主动拆卸的组件,也能够思考用计划一

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-registrationcenter-switch

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理