zoukankan      html  css  js  c++  java
  • 聊聊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

  • 相关阅读:
    团队项目推荐:附近商家无地图导航——by Manni
    团队项目推荐:基于社交网络的自动音乐推荐系统——by ZhongXia
    往届项目回顾:自动字幕对轴软件Autosub
    【Daily Scrum】11-19
    【Review】Review of Sprint 1& Sprint 2 planning
    【Daily Scrum】11-17 Summary of Sprint 1
    【Daily Scrum】11-13
    【Daily Scrum】11-12
    【Daily Scrum】11-11
    【Daily Scrum】11-7
  • 原文地址:https://www.cnblogs.com/linyb-geek/p/15095031.html
Copyright © 2011-2022 走看看