Eureka 系列(03)Spring Cloud 自动装配原理
[TOC]
0. Spring Cloud 系列目录 - Eureka 篇
本文主要是分析 Spring Cloud 是如何整合 Eureka 的,但不会具体分析 Eureka 的源码,之后的文章会对 Eureka 的源码做一个比较具体的分析。
1. Eureka Client 自动装配
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
总结: Eureka Client 的装配很简单,主要是组装 DiscoveryClient。
EurekaClientAutoConfiguration
主要是装配 EurekaClientEurekaDiscoveryClientConfiguration
主要是在 Eureka Client 启动时立即将自身注册到 Eureka Server 上。
1.1 装配 DiscoveryClient
EurekaClientAutoConfiguration
主要是装配 CloudEurekaClient,CloudEurekaClient 继承了 DiscoveryClient,主要是增加了 Spring 的事件机制。
@Bean(destroyMethod = "shutdown")
public EurekaClient eurekaClient(ApplicationInfoManager manager,
EurekaClientConfig config, EurekaInstanceConfig instance,
@Autowired(required = false) HealthCheckHandler healthCheckHandler) {
CloudEurekaClient cloudEurekaClient = new CloudEurekaClient(
appManager, config, this.optionalArgs, this.context);
cloudEurekaClient.registerHealthCheck(healthCheckHandler);
return cloudEurekaClient;
}
1.2 启动时立即注册
EurekaDiscoveryClientConfiguration
主要是实现了自动注册。在 DiscoveryClient 中默认是启动 40s 后才会注册,延迟太长,Spring Cloud 改变了这种默认实现,在启动时调用 EurekaAutoServiceRegistration.start()
,将自身实例注册到 Eureka Server 上。
// EurekaAutoServiceRegistration 启动时自动注册
public void start() {
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);
}
}
1.3 替换 EurekaHttpClient
Spring Cloud 实现了自己的 RestTemplateEurekaHttpClient,可以替换默认的 JerseyApplicationClient。DiscoveryClientOptionalArgsConfiguration 中装配条件如下:
@Bean
@ConditionalOnMissingClass("com.sun.jersey.api.client.filter.ClientFilter")
@ConditionalOnMissingBean(value = AbstractDiscoveryClientOptionalArgs.class, search = SearchStrategy.CURRENT)
public RestTemplateDiscoveryClientOptionalArgs restTemplateDiscoveryClientOptionalArgs() {
return new RestTemplateDiscoveryClientOptionalArgs();
}
查一下 com.sun.jersey 的依赖情况,可以看到 eureka-client 默认会引入 jersey-client,也就是说会使用默认的 JerseyApplicationClient。
>mvn dependency:tree -Dincludes="com.sun.jersey"
[INFO] com.github.binarylei.springcloud:user-consumer-eureka-client:jar:1.0.0
[INFO] \- org.springframework.cloud:spring-cloud-starter-netflix-eureka-client:jar:2.1.1.RELEASE:compile
[INFO] \- com.netflix.eureka:eureka-client:jar:1.9.8:compile
[INFO] +- com.sun.jersey:jersey-core:jar:1.19.1:runtime
[INFO] \- com.sun.jersey:jersey-client:jar:1.19.1:runtime
既然知道了原因,要替换为 RestTemplateEurekaHttpClient 就很简单了。
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
com.sun.jersey
jersey-client
分析了客户自动装配后,接下来继续分析 Eureka Server 服务端的启动原理,Eureka 服务端的启动同样依赖 DiscoverClient(个人感觉依赖有点混乱)。
2. Eureka Server 自动装配
在原生的 Eureka 中服务端的启动类是 EurekaBootStrap,Spring Cloud 中启动类是 EurekaServerBootstrap,原理大致都差不多。本文会以 EurekaServerBootstrap 为切入点进行分析。
EurekaServerAutoConfiguration 是 Spring Cloud 自动装配入口,配置如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration
注意: EurekaServerAutoConfiguration 需要配合 @EnableEurekaServer 使用。
2.1 Eureka Server 自动装配流程
总结: Spring Cloud 有个特点,一般来说类的职责都很明确。
EurekaServerAutoConfiguration
主要是装配 EurekaServerBootstrapEurekaServerInitializerConfiguration
则负责启动 EurekaServerBootstrapEurekaServerBootstrap
启动时主要完成两件事:一是从其它 Eureka Server 上同步数据;二是启动自动过期定时任务 EvictionTask。可以看到 Eureka 最核心的数据结构是 PeerAwareInstanceRegistry。
Eureka Server 核心实现类:
PeerAwareInstanceRegistry
负责 Eureka Server 之间数据同步,其父类 AbstractInstanceRegistry 则管理所有的本地注册信息。PeerEurekaNodes
负责 Eureka Server 服务器列表管理。EurekaServerBootstrap
启动类。EurekaClient
上文提到 Eureka Server 启动是要依赖 Eureka Client 客户端,所以也会自动装配 EurekaClient,启动时同步数据会依赖 EurekaClient。
2.2 EurekaServerBootstrap
EurekaServerInitializerConfiguration 实现了 SmartLifecycle 接口,也就是启动和销毁时会分别调用 start 和 stop 方法。
public void start() {
new Thread(new Runnable() {
@Override
public void run() {
try {
// TODO: is this class even needed now?
eurekaServerBootstrap.contextInitialized(
EurekaServerInitializerConfiguration.this.servletContext);
log.info("Started Eureka Server");
publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
EurekaServerInitializerConfiguration.this.running = true;
publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
}
catch (Exception ex) {
// Help!
log.error("Could not initialize Eureka servlet context", ex);
}
}
}).start();
}
public void stop() {
this.running = false;
eurekaServerBootstrap.contextDestroyed(this.servletContext);
}
继续关注 EurekaServerBootstrap 的 contextInitialized 和 contextDestroyed 方法分别完成了什么事情。
public void contextInitialized(ServletContext context) {
initEurekaEnvironment();
initEurekaServerContext();
}
protected void initEurekaServerContext() throws Exception {
...
// Copy registry from neighboring eureka node
int registryCount = this.registry.syncUp();
this.registry.openForTraffic(this.applicationInfoManager, registryCount);
// Register all monitoring statistics.
EurekaMonitors.registerAllStats();
}
总结: 可以看到 EurekaServerBootstrap 启动时主要步骤:一是同步数据;二是启动定时过期的任务 EvictionTask。具体的源码会在之后分析。
每天用心记录一点点。内容也许不重要,但习惯很重要!