Eureka Server启动过程
前言
我们已经在 Spring Cloud注册发现:Eureka 一文中对Eureka的基本原理有所了解,并在 Eureka使用示例 一文中我们简单介绍了如何创建一个 Eureka Server 和 Client,并将Client注册到Server中。其中有介绍到:@EnableEurekaServer 注解开启Eureka Server的服务注册功能。下面我们就以@EnableEurekaServer 为入口,来分析一下Eureka Server的启动流程。
源码解析
EnableEurekaServer注解位于 org.springframework.cloud.netflix.eureka.server 包中
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {
}
可以看到它导入了EurekaServerMarkerConfiguration类:
@Configuration
public class EurekaServerMarkerConfiguration {
@Bean
public Marker eurekaServerMarkerBean() {
return new Marker();
}
class Marker {
}
}
这个EurekaServerMarkerConfiguration会往Spring容器中注入一个eurekaServerMarkerBean,这个Marker是一个空类,那在这里起到什么作用呢?
仔细查看这个类的注释,可以看到它只是一个开关标记,用来激活EurekaServerAutoConfiguration类的。
那Eureka Server真正的启动入口在哪儿呢?
了解 Spring Factories 机制的同学可能会想到去查看 spring-cloud-starter-netflix-eureka-server
jar包中的 META-INF/spring.factories
文件,内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration
可以看到 Eureka Server 启动的真正入口是 EurekaServerAutoConfiguration
:
@Configuration
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter
- @Configuration注解表示这是一个配置类,通过@Bean注解声明一些注入到Spring IOC容器中的Bean。
- @ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class),表示只要Spring容器中有EurekaServerMarkerConfiguration.Marker.class类的实例存在,那么就会将这个EurekaServerAutoConfiguration也注入到Spring容器中。
- @Import(EurekaServerInitializerConfiguration.class)表明它导入了EurekaServerInitializerConfiguration这个类。
此外,这个EurekaServerAutoConfiguration继承自WebMvcConfigurer,可以用来定义Spring MVC的一些配置。
在这个类中,我们并没有发现与启动Eureka相关的代码,那么我们来看看它引入的这个EurekaServerInitializerConfiguration:
@Configuration
public class EurekaServerInitializerConfiguration
implements ServletContextAware, SmartLifecycle, Ordered
可以看到,这也是一个配置类,同时它实现了 ServletContextAware接口,可以在Servlet容器启动后得到ServletContext容器上下文;它还实现了 SmartLifecycle
,这样在spring 生命周期中会调用这个类相关的方法。比如在spring初始化时,会调用它start方法。
@Override
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();
}
contextInitialized方法是真正执行了Eureka Server的初始化过程:
public void contextInitialized(ServletContext context) {
try {
initEurekaEnvironment();
initEurekaServerContext();
context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
}
catch (Throwable e) {
log.error("Cannot bootstrap eureka server :", e);
throw new RuntimeException("Cannot bootstrap eureka server :", e);
}
}
initEurekaEnvironment() 方法从配置文件中读取并设置datacenter和environment的配置参数,过程比较简单就不再赘述。
initEurekaServerContext() 方法初始化 EurekaServer 上下文:
protected void initEurekaServerContext() throws Exception {
// 省略AWS相关逻辑
EurekaServerContextHolder.initialize(this.serverContext);
log.info("Initialized server context");
// 从邻近的Eureka Server同步注册信息
int registryCount = this.registry.syncUp();
this.registry.openForTraffic(this.applicationInfoManager, registryCount);
// 注册所有监控统计信息
EurekaMonitors.registerAllStats();
}
registry.syncUp() 方法用于在当前 Eureka Server 节点启动时从邻近的Eureka Server同步注册信息,并返回同步得到的应用数量。当存在多个Eureka Server时,该方法会有实际的作用,用于达到各个节点之间数据的最终一致性。可以通过 eureka.server.registry-sync-retries
配置调整同步重试次数。如果未获取到应用实例,则 Eureka-Server 会有一段时间( 默认:5 分钟,可配 )不允许被 Eureka-Client 获取注册信息,避免影响 Eureka-Client 。详细过程请参考:Eureka-Server 集群同步。
registry.openForTraffic() 方法
在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。
@Override
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
// 计算每分钟最小续租次数。例如,如果30s续租一次,
// 那么每分钟最小次数为2。
this.expectedNumberOfClientsSendingRenews = count;
updateRenewsPerMinThreshold();
logger.info("Got {} instances from neighboring DS node", count);
logger.info("Renew threshold is: {}", numberOfRenewsPerMinThreshold);
this.startupTime = System.currentTimeMillis();
if (count > 0) {
this.peerInstancesTransferEmptyOnStartup = false;
}
DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
// 如果数据中心为 AWS,则为所有副本启动AWS连接
boolean isAws = Name.Amazon == selfName;
if (isAws && serverConfig.shouldPrimeAwsReplicaConnections()) {
logger.info("Priming AWS connections for all replicas..");
primeAwsReplicas(applicationInfoManager);
}
logger.info("Changing status to UP");
// 更新实例状态为UP
applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
super.postInit();
}
此时,Eureka Server已经启动,开始接受Eureka Client的注册、续租以及查询请求。为了保证Eureka Client的健康存活,Eureka Server会启动定时任务清理租约信息,淘汰失效的Eureka Client。
protected void postInit() {
// 启动定时任务,定期清理租约信息
renewsLastMin.start();
if (evictionTaskRef.get() != null) {
evictionTaskRef.get().cancel();
}
//
evictionTaskRef.set(new EvictionTask());
evictionTimer.schedule(evictionTaskRef.get(),
serverConfig.getEvictionIntervalTimerInMs(),
serverConfig.getEvictionIntervalTimerInMs());
}