引言

我们知道,使用 Spring Cloud 开发微服务时,服务注册的使用方式非常简单,只需要引入服务注册的依赖即可。

<dependencies>
    <dependency>
    	<groupId>org.springframework.cloud</groupId>        
	    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>                		 <version>0.9.0.RELEASE</version>                 
    </dependency>    
    <dependency>        
    	<groupId>org.springframework.boot</groupId>        
    	<artifactId>spring-boot-starter-web</artifactId>    
    </dependency>
</dependencies>
<dependencyManagement>    
	<dependencies>        
		<dependency>            
			<groupId>org.springframework.cloud</groupId>            
			<artifactId>spring-cloud-dependencies</artifactId>            		  <version>Greenwich.SR1</version>            <type>pom</type>            <scope>import</scope>        </dependency>    </dependencies></dependencyManagement>

但是有些情况下,我们会有将一个 Spring Cloud 应用注册到多个服务注册中心的需求。这时候如果简单地在依赖中添加两个服务注册组件的依赖,则应用在启动阶段就会报错,导致启动失败。为什么不能多注册?
首先,我们在 Spring Cloud 应用中引入两个服务注册组件的依赖,重现一下启动失败的场景。

<dependencies>    <dependency>        <groupId>org.springframework.cloud</groupId>        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>        <version>0.9.0.RELEASE</version>    </dependency>    <dependency>        <groupId>org.springframework.cloud</groupId>        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency></dependencies>

启动 main 方法,报错的信息如下所示。

***************************APPLICATION FAILED TO START***************************Description:Field autoServiceRegistration in org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration required a single bean, but 2 were found:    
- nacosAutoServiceRegistration: defined by method 'nacosAutoServiceRegistration' in class path resource org/springframework/cloud/alibaba/nacos/NacosDiscoveryAutoConfiguration.class] 
- eurekaAutoServiceRegistration: defined by method 'eurekaAutoServiceRegistration' in class path resource [org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.class]Action:Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consume

看日志可以发现启动失败的原因是因为 AutoServiceRegistrationAutoConfiguration 这个类需要自动注入一个类型为 AutoServiceRegistration 的 bean。

但是在 Spring 容器中,发现了两个父类为 AutoServiceRegistration 的 bean,分别是 nacosAutoServiceRegistration 和 eurekaAutoServiceRegistration。这样就导致了自动注入时不知道应该选择使用哪个 bean,进而导致了应用启动失败。提示的解决方案是将其中的一个 bean 标记为 @Primary,但是我们既无法修改 netflix-eureka-client 的源码,又无法修改 alibaba-nacos-discovery 的源码,而且我们还不能修改 AutoServiceRegistrationAutoConfiguration 所处于的 spring-cloud-commons 的源码。

没办法解决了吗?既然无法修改他们的源码,那我们现在换一个思路,我们将 AutoServiceRegistrationAutoConfiguration这个类从 autoconfigure 中排除。使用如下方法,将其排除,在 application.properties 中添加如下配置,然后重新启动应用。spring.autoconfigure.exclude=org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration
1.

日志表明两边都注册成功了,登录控制台查看,也确实是如此。2019-04-22 11:12:37.050 INFO 29189 — [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_OPENSOURCE-SERVICE-PROVIDER/192.168.0.2:opensource-service-provider:18082: registering service…2019-04-22 11:12:37.089 INFO 29189 — [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_OPENSOURCE-SERVICE-PROVIDER/192.168.0.2:opensource-service-provider:18082 - registration status: 2042019-04-22 11:12:37.109 INFO 29189 — [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 18082 (http) with context path ''2019-04-22 11:12:37.110 INFO 29189 — [ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 180822019-04-22 11:12:37.119 INFO 29189 — [ main] o.s.c.a.n.registry.NacosServiceRegistry : nacos registry, opensource-service-provider 192.168.0.2:18082 register finished2019-04-22 11:12:37.123 INFO 29189 — [ main] c.a.demo.provider.ProviderApplication : Started ProviderApplication in 4.352 seconds (JVM running for 4.928)
1.
这样就解决了?
虽然直接 AutoServiceRegistrationAutoConfiguration这个类从 autoconfigure 中排除可以注册成功了。但是这样做不会有什么副作用,或者影响其他功能吗?心里感觉没底,还是有点慌,对不对?别慌,我们来看一下这个类的源码。1.

@Configuration

@Import(AutoServiceRegistrationConfiguration.class)

@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)

public class AutoServiceRegistrationAutoConfiguration {



    @Autowired(required = false)

    private AutoServiceRegistration autoServiceRegistration;



    @Autowired

    private AutoServiceRegistrationProperties properties;



    @PostConstruct

    protected void init() {

        if (autoServiceRegistration == null && this.properties.isFailFast()) {

            throw new IllegalStateException("Auto Service Registration has been requested, but there is no AutoServiceRegistration bean");

        }

    }

}

重点关注这两个部分 @Import(AutoServiceRegistrationConfiguration.class) 和 init方法。
init 方法
首先看 init方法。它的逻辑是做一个检查,如果 autoServiceRegistration 为空且 AutoServiceRegistrationProperties 的 failFast 属性为 true 的情况下,就直接抛出 IllegalStateException 异常。没事,我们现在的问题就是因为 AutoServiceRegistration 太多了。而且 AutoServiceRegistrationProperties 中的 failFast 字段默认值是 false,除非你配置了为 true,否则这段逻辑本身也不会执行。总结一下,从 init方法 来看,将 AutoServiceRegistrationAutoConfiguration 排除相当于使 AutoServiceRegistrationProperties 中的 failFast 字段失效。如果你真的对这个配置有特别强的需求,那么你可以在手动排除后自行加上这块逻辑。但是在笔者看来完全没必要,无非就是在后面会更晚的阶段抛出另外一个异常而已。@Import(AutoServiceRegistrationConfiguration.class)
然后我们再看看看 @Import(AutoServiceRegistrationConfiguration.class) 的逻辑。@Configuration@EnableConfigurationProperties(AutoServiceRegistrationProperties.class)@ConditionalOnProperty(value = “spring.cloud.service-registry.auto-registration.enabled”, matchIfMissing = true)public class AutoServiceRegistrationConfiguration {}
1.
AutoServiceRegistrationConfiguration 这个类其实就只做了一件事,实例化一个 AutoServiceRegistrationProperties 的 bean。AutoServiceRegistrationProperties 的作用非常关键,我们在NacosDiscoveryAutoConfiguration、 ConsulAutoServiceRegistrationAutoConfiguration 以及 EurekaClientAutoConfiguration 这三个类的实现中都可以看到 ConditionalOnBean(AutoServiceRegistrationProperties.class) 这样的关键代码。可以说, ConditionalOnBean(AutoServiceRegistrationProperties.class) 是服务注册的开关。那问题来了,为什么我们把他排除了之后,应用不仅启动成功了,还分别成功注册到两个注册中心了呢?下载了 spring-cloud-common 的源码,对着 AutoServiceRegistrationProperties 点击右键,选择使用 Find Usages,在下方找一下 Usagein.class 和 Newinstance creation,并没有找到其他实例化 AutoServiceRegistrationProperties 的使用。那这个 bean 到底是在什么情况下实例化的呢?换个思路,既然这个 bean 只能通过 AutoServiceRegistrationConfiguration 这个类来实例化,那么我们找找 AutoServiceRegistrationConfiguration 还在那里被使用到了。继续对着 AutoServiceRegistrationConfiguration 点击右键,选择使用 Find Usages,依旧没有找到。最后没办法,使用全文搜索试试,终于找到了如下代码片段,下面的引用只保留了关键的部分。1

@Order(Ordered.LOWEST_PRECEDENCE - 100)

public class EnableDiscoveryClientImportSelector extends SpringFactoryImportSelector<EnableDiscoveryClient> {
    @Override
    public String[] selectImports(AnnotationMetadata metadata) {

        String[] imports = super.selectImports(metadata);
       AnnotationAttributes attributes = AnnotationAttributes.fromMap(
                metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));
        boolean autoRegister = attributes.getBoolean("autoRegister");
        if (autoRegister) {
            List<String> importsList = new ArrayList<>(Arrays.asList(imports));
            importsList.add(          "org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");            imports = importsList.toArray(new String[0]);

        }else {  
            .........        }       
        return imports;    }   
    .........}

我们在看看 ImportSelector 这个接口对于 selectImports(AnnotationMetadataimportingClassMetadata) 方法的注释。

public interface ImportSelector {

    /**

     * Select and return the names of which class(es) should be imported based on

     * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.

     */

    String[] selectImports(AnnotationMetadata importingClassMetadata);



}

从这段代码逻辑中可以看到,只要引入了 @EnableDiscoveryClient,且没有显示地指定 autoRegister 为 false,那么就会引入 AutoServiceRegistrationConfiguration 这个 Configuration。总结一下,从 @Import(AutoServiceRegistrationConfiguration.class) 这部分来看,将 AutoServiceRegistrationAutoConfiguration 排除后,则必须要存在@EnableDiscoveryClient 注解,且没有显示地指定 autoRegister 为 false,服务才能自动注册。总结
通过刚才的分析,我们重述一下将 AutoServiceRegistrationAutoConfiguration 排除后的影响面。AutoServiceRegistrationProperties 中的 failFast 字段失效必须要存在 @EnableDiscoveryClient 注解,且没有显示地指定 autoRegister 为 false,服务才能自动注册。看到这里,我们应该定位到了问题的影响面。除非对于上述的两点有特殊的需求,在 spring.autoconfigure 中 exclude 掉 AutoServiceRegistrationAutoConfiguration,不会有其他副作用。更进一步
1.刚才演示的是一个最基础的场景。一般来说,我们的 spring boot 应用都会使用 spring-boot-starter-actuator,当存在这个依赖时,即使执行了上文的操作,启动时还是报错。这该怎么办?根据报错信息定位到是 ServiceRegistryAutoConfiguration 这个类,接着排除就可以,至于排除后会产生哪些影响,监控会少一个 Endpoint,这里就不具体分析了。2.在配置文件中填写 spring.autoconfigure.exclude 中添加类比较麻烦,还有其他办法吗?在代码中排除,@SpringBootApplication(exclude=SecurityAutoConfiguration.class)通过 AutoConfigurationImportFilter 来排除重点讲一下第二种方法1.

public class RegistryExcludeFilter implements AutoConfigurationImportFilter {
   private static final Set<String> SHOULD_SKIP = new HashSet<>( Arrays.asList("org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration",      "org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration"));

   @Override
   public boolean[] match(String[] classNames, AutoConfigurationMetadata metadata) {
       boolean[] matches = new boolean[classNames.length];
       for (int i = 0; i < classNames.length; i++) {
           matches[i] = !SHOULD_SKIP.contains(classNames[i]);
       }
       return matches;
   }
}

看起来这样是麻烦了一些,多了一步,但是我们可以将这些修改放在一个 base 包中,业务开发时只需要引入这个 base 包即可。3.使用场景讲了这么多,照应一下开头,到底是什么场景会有需要注册到多个注册中心的需求呢?我们目前看到的场景是迁移注册中心的时候会有这个需求。当应用需要进行迁移时,如何保证业务不中断是重中之重。而服务注册中心与服务调用强相关,可以说服务注册中心的平滑迁移是应用平滑迁移的基础。也许你不想进行上述的那么多操作,而是想直接体验多注册的特性。
笔者已经基于上面说的第二种方法完成了一个 base 包,且同时支持 Spring Boot/Cloud 的各个版本,直接引入下面的依赖,用起来吧。

<dependency>       
	<groupId>com.alibaba.edas</groupId>       
	<artifactId>edas-sc-migration-starter</artifactId>       
	<version>1.0.1</version>
</dependency>

如何解决feign调用或者远程调用

需要重写ribbon,找到一篇博客,大家可参考
https://www.cnblogs.com/Micheal-Ding/p/14864942.html
摘自:https://blog.51cto.com/u_15127675/2819286

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐