• 聊聊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

  • 相关阅读:
    HDU 2852 KiKi's K-Number (主席树)
    HDU 2089 不要62
    Light oj 1140 How Many Zeroes?
    Bless You Autocorrect!
    HDU 6201 transaction transaction transaction
    HDU1561 The more ,The better (树形背包Dp)
    CodeForces 607B zuma
    POJ 1651 Mulitiplication Puzzle
    CSUOJ 1952 合并石子
    Uva 1599 Ideal path
  • 原文地址:https://www.cnblogs.com/linyb-geek/p/15095031.html
Copyright © 2020-2023  润新知