前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊聊springcloud项目同时存在多个注册中心客户端采坑记

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

作者头像
lyb-geek
发布2021-09-02 15:27:33
4950
发布2021-09-02 15:27:33
举报
文章被收录于专栏:Linyb极客之路Linyb极客之路

1

前言

前段时间业务部门有这么一个业务场景,他们自己微服务注册中心是用eureka,他们有一些服务接口要调用兄弟部门的接口,他们定了一个服务调用方案,业务部门直接把他们服务注册到兄弟部门的注册中心,然后走rpc调用,兄弟部门注册中心是用nacos。

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

代码语言:javascript
复制
<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>

然后项目启动报了如下错

代码语言:javascript
复制
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引入多个注册中心客户端,项目也要能正常启动

2

需求分析

从项目异常分析

代码语言:javascript
复制
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,我们想选的注册中心

3

实现方案

目前基本只要和springboot集成的开源项目,可以说大部分使用了自动装配,因此我们的解决思路也是从自动装配搞起

前置知识

代码语言:javascript
复制
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);
  }
代码语言:javascript
复制
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的自动装配

01

核心实现

代码语言:javascript
复制
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;
    }


}

02

配置spring.factories

代码语言:javascript
复制
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
com.github.lybgeek.registration.autoconfigure.filter.RegistrationCenterAutoConfigurationImportFilter

方案二:利用application-${指定注册中心标识} + spring.profiles.active

01

在要激活的注册中心的文件禁用其他注册中心客户端

比如appliication-nacos.yml禁用eureka

代码语言:javascript
复制
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

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

02

激活想要注册的注册中心

代码语言:javascript
复制
spring:
  application:
    name: springboot-registration-client
  profiles:
    active: nacos

4

总结

这两种方案个人是比较推荐方案二,因为改动最小。方案一比较适用于没有提供是否需要激活注册中心开关的注册中心。其次如果我们要排除某些开源自动装配的组件,也可以考虑用方案一

5

demo链接

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-08-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Linyb极客之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档