背景
在生产环境,每次换完版,都发现zuul的日志有服务拒绝连接的情况,表明服务根本就没有启动成功,初步怀疑是没有优雅的下线微服务导致的。
优雅的下线微服务
通过Eureka提供的端点,进行优雅的下线微服务,并且暂停40s等服务消费者从新从Eureka获取服务列表(默认30s),后面才kill掉服务(最好的方式是等应用没有流量进来再kill掉微服务),然后进行重启。
发现重启完后zuul还是会有几个这样拒绝连接的日志!
原因猜测
会不会是SpringCloud在启动的过程中,应用还没有启动完就向Eureka服务端注册服务,然后导致在发送注册消息到应用真正启动完的时间段内,请求进来会报拒绝连接的错误,这个得查看源码才能知道。
源码分析
有了解过SpringBoot自动配置的同学应该知道,在主程序启动类上我们会贴一个 @SpringBootApplication 注解,该注解里面包含了一个 @EnableAutoConfiguration 注解,该注解的作用是开启SpringBoot的自动配置,即:在 @EnableAutoConfiguration 标签中使用了一个 AutoConfigurationImportSelector 选择器,该选择器会去扫描当前classpath环境中的 META-INF/spring.factories 文件中的自动配置类,然后完成相关的自动配置。
在 selectImports 方法中会将扫描到的 META-INF/spring.factories 文件中的自动配置的一些类加载进来,然后注册到Spring容器中
其中在 spring-cloud-netflix-eureka-client-2.0.1.RELEASE.jar 这个jar包里面就有一个 META-INF/spring.factories文件,内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
我们这里需要重点关注是 EurekaClientAutoConfiguration 这个类,从名字就能看出它是对EureakClient的自动配置,可以看到有如下代码
//注册Eureak客户端EurekaClient, @Lazy 懒初始化,该bean被用到的时候会被创建
//EurekaClient有一个实现 DiscoveryClient这个实现 是eureka-client:1.9.3包里面的,是Euraek的服务发现的核心实现
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
@org.springframework.cloud.context.config.annotation.RefreshScope
@Lazy
public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config, EurekaInstanceConfig instance) {
manager.getInfo(); // force initialization
return new CloudEurekaClient(manager, config, this.optionalArgs,
this.context);
}
但是这里有个注解@Lazy,延迟加载,也就是不是在这里初始化的,我们再看EurekaAutoServiceRegistration,这个类实现了SmartLifecycle,然后在应用启动后就会自动调用start方法,代码如下:
@Override
public void start() {
// only set the port if the nonSecurePort or securePort is 0 and this.port != 0
if (this.port.get() != 0) {
if (this.registration.getNonSecurePort() == 0) {
this.registration.setNonSecurePort(this.port.get());
}
if (this.registration.getSecurePort() == 0 && this.registration.isSecure()) {
this.registration.setSecurePort(this.port.get());
}
}
// only initialize if nonSecurePort is greater than 0 and it isn't already running
// because of containerPortInitializer below
if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
//调用服务注册器进行服务注册
this.serviceRegistry.register(this.registration);
//发布一个服务注册事件
this.context.publishEvent(
new InstanceRegisteredEvent<>(this, this.registration.getInstanceConfig()));
this.running.set(true);
}
}
可以知道,这里也进行了加载,所以是在应用启动后才去注册的,应该没问题啊!
那为什么还会出现上面的问题?
原来,项目里的微服务Eureka版本是1.2.6,springcloud还没有加上EurekaAutoServiceRegistration类,然后是靠自动配置类的如下代码:
@Bean
public DiscoveryClient discoveryClient(EurekaInstanceConfig config, EurekaClient client) {
return new EurekaDiscoveryClient(config, client);
}
第二个入参是com.netflix.discovery.EurekaClient类型,此对象同样来自EurekaClientAutoConfiguration类,如下方法:
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
@org.springframework.cloud.context.config.annotation.RefreshScope
@Lazy
public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config, EurekaInstanceConfig instance) {
manager.getInfo(); // force initialization
return new CloudEurekaClient(manager, config, this.optionalArgs,this.context);
}
疑问,我看新版本的Eureka也有上面的方法,那为啥还要EurekaAutoServiceRegistration这个,要了也没有意义啊!这里真是搞不懂了,只能够推测新版本的这段逻辑不会执行!