前言
对于一个微服务架构来说,通常都会将一个完整的项目拆分成许多个小的服务,而服务之间如何通讯以及服务如何治理是最需要解决的问题。而Eureka就是一种解决微服务架构中最基本的服务治理的一种技术。SpringCloud架构中Eureka作为服务治理的组件,管理各种服务功能包括服务的注册与发现。对于学习SpringCloud框架来说,还是需要对Eureka有一些了解的。下文将深入分析Eureka的一些核心源码,帮忙读者更多的了解Eureka这个组件。
源码分析
核心入口
Spring Cloud组件的加载也是按照SpringBoot的自动配置规范进行引入的。对于Eureka来说,它会在对应的jar中的META-INF下的spring.factories文件中,指定一系列的自动配置类,完成相应组件的自动加载
对于Eureka Client端,在客户端启动时,最为关键的是加载EurekaClientAutoConfiguration这个自动配置类
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@Import(DiscoveryClientOptionalArgsConfiguration.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@AutoConfigureBefore({
NoopDiscoveryClientAutoConfiguration.class,
CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
@AutoConfigureAfter(name = {
"org.springframework.cloud.autoconfigure.RefreshAutoConfiguration",
"org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
"org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration" })
public class EurekaClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean(value = EurekaClientConfig.class,
search = SearchStrategy.CURRENT)
public EurekaClientConfigBean eurekaClientConfigBean(ConfigurableEnvironment env) {
//eureka客户端的配置类,可以在该类内部看到客户端的配置信息
EurekaClientConfigBean client = new EurekaClientConfigBean();
if ("bootstrap".equals(this.env.getProperty("spring.config.name"))) {
client.setRegisterWithEureka(false);
}
return client;
}
....
}
该自动配置类内部又包含了EurekaClientConfiguration内部类,这个内部类通过@Bean的方式注入了一个CloudEurekaClient实例,查看CloudEurekaClient的类图可以发现它继承了DiscoveryClient类,而DiscoveryClient是eureka客户端最关键的一个类
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingRefreshScope
protected static class EurekaClientConfiguration {
@Autowired
private ApplicationContext context;
@Autowired
private AbstractDiscoveryClientOptionalArgs<?> optionalArgs;
//创建EurekaClient实例
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(value = {
EurekaClient.class}, search = SearchStrategy.CURRENT)
public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config) {
//核心,创建CloudEurekaClient
return (EurekaClient)new CloudEurekaClient(manager, config, this.optionalArgs, (ApplicationEventPublisher)this.context);
}
....
}
public class CloudEurekaClient extends DiscoveryClient {
...
}
当CloudEurekaClient类被实例化时就会触发父类的DiscoveryClient实例化,然后就会执行到下面的这个构造函数。而这个构造函数中会做很多事情,包括:
(1)将客户端配置文件进行解析,然后将客户端配置信息封装到EurekaClientConfigBean对象中
(2)创建scheduler、heartbeatExecutor、cacheRefreshExecutor线程池
(3)创建定时任务,提交到线程池,进行服务注册、续约和服务拉取等功能
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
...
if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
...
return;
}
try {
AzToRegionMapper azToRegionMapper;
//定时任务线程池,负责服务注册、续约和服务拉取
this.scheduler = Executors.newScheduledThreadPool(2, (new ThreadFactoryBuilder())
.setNameFormat("DiscoveryClient-%d")
.setDaemon(true)
.build());
//发送心跳的线程池
this
.heartbeatExecutor = new ThreadPoolExecutor(1, this.clientConfig.getHeartbeatExecutorThreadPoolSize(), 0L, TimeUnit.SECONDS, new SynchronousQueue<>(), (new ThreadFactoryBuilder()).setNameFormat("DiscoveryClient-HeartbeatExecutor-%d").setDaemon(true).build());
//执行程序缓存刷新线程池
this
.cacheRefreshExecutor = new ThreadPoolExecutor(1, this.clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0L, TimeUnit.SECONDS, new SynchronousQueue<>(), (new ThreadFactoryBuilder()).setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d").setDaemon(true).build());
....
} catch (Throwable e) {
throw new RuntimeException("Failed to initialize DiscoveryClient!", e);
}
...
//核心:服务拉取、服务注册和续约的实现
initScheduledTasks();
try {
Monitors.registerObject(this);
} catch (Throwable e) {
logger.warn("Cannot register timers", e);
}
...
}
服务拉取
先判断客户端是否配置了服务拉取eureka.client.fetchRegistry配置项,如果配置为false就不进行服务拉取,缺省值为true。服务拉取过程中会向定义的scheduler线程池提交一个定时任务
private void initScheduledTasks() {
//确认是否配置eureka.client.fetch-registry=true 缺省为 true
if (this.clientConfig.shouldFetchRegistry()) {
int registryFetchIntervalSeconds = this.clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = this.clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
//向scheduler线程池提交一个拉取任务
this.scheduler.schedule(new TimedSupervisorTask("cacheRefresh", this.scheduler, this.cacheRefreshExecutor, registryFetchIntervalSeconds, TimeUnit.SECONDS, expBackOffBound, new CacheRefreshThread()), registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
....
}
class CacheRefreshThread implements Runnable {
public void run() {
DiscoveryClient.this.refreshRegistry();
}
}
之后,任务执行时就会调用到refreshRegistry方法,该方法内部又会调用fetchRegistry方法进行真正的服务拉取工作。而服务拉取过程中会先判断客户端配置的eureka.client.disable-delta配置项的值,确认是全量拉取还是增量拉取
注意,增量拉取可以极大地减少流量,因为eureka服务器的更改速率通常远低于提取速率
void refreshRegistry() {
try {
...
//服务拉取实现
boolean success = fetchRegistry(remoteRegionsModified);
if (success) {
this.registrySize = ((Applications)this.localRegionApps.get()).size();
this.lastSuccessfulRegistryFetchTimestamp = System.currentTimeMillis();
}
....
}
} catch (Throwable e) {
logger.error("Cannot fetc