概述
Spring Cloud中默认的服务发现采用的netflix的eureka,本篇文章就是阅读Spring cloud中通过eureka做服务发现时的笔记。
顺藤摸瓜
读取Spring各种框架的时候,很多时候不知道从什么地方开始,因为Spring中很多模块的开启就是通过一行注解,例如@EnableXXX。而Spring Cloud中的配置服务则是通过@EnableDiscoveryClient,(其实@EnableEurekaClient就是@EnableDiscoveryClient)
/**
* Annotation to enable a DiscoveryClient implementation.
* @author Spencer Gibb
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {
/**
* If true, the ServiceRegistry will automatically register the local server.
*/
boolean autoRegister() default true;
}
上面有一个@Import(EnableDiscoveryClientImportSelector.class),然后看一下EnableDiscoveryClientImportSelector这个类
public class EnableDiscoveryClientImportSelector
extends SpringFactoryImportSelector<EnableDiscoveryClient> {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
...
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]);
}
return imports;
}
}
一看代码第一反应就是引入了org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration这个类,再看一下这个类
@Configuration
@EnableConfigurationProperties(AutoServiceRegistrationProperties.class)
public class AutoServiceRegistrationConfiguration {
}
什么都没有,怎么回事。其实机关就是在SpringFactoryImportSelector<EnableDiscoveryClient>这个类。其实他会把类全命名作为自动配置类的key,了解Spring Boot自动加载过程的就应该知道在某个jar包下META-INFO/spring.factories。
org.springframework.cloud.client.discovery.EnableDiscoveryClient=\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
这样,Spring Cloud启动的时候初始化Eureka就是在EurekaDiscoveryClientConfiguration这个类中。仔细一看,也不对啊,这个类只注册了一个Marker类,而且这个类是个空类。再全局搜索一下哪里引用了这个Marker类,终于发现真正初始化Eureka的类:EurekaClientAutoConfiguration
@Configuration
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
@AutoConfigureAfter(name = "org.springframework.cloud.autoconfigure.RefreshAutoConfiguration")
public class EurekaClientAutoConfiguration
追根溯源
既然找到了EurekaClientAutoConfiguration这个配置类,那么肯定有初始化和NetFlix相关类,仔细一看,就是
@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);
}
终于发现和NetFlix的链接的地方,CloudEurekaClient继承的DiscoveryClient其实就是NetFlix的服务发现类,这样,就可以好好分析是怎么初始化的。
直击要害
CloudEurekaClient在初始化的时候主要是调用父类DiscoveryClient的构造函数,会做很多事,其中注册服务和发现服务是通过调度任务来完成,调度任务的初始化是在initScheduledTasks这个而方法中,其中服务发现的代码
ate void initScheduledTasks() {
if (clientConfig.shouldFetchRegistry()) {
// registry cache refresh timer
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread()
),
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
首先判断是否需要进行服务发现,然后通过一个定时任务去刷新缓存信息。TimedSupervisorTask是支持timeout的调度任务,刷新缓存逻辑实际是在CacheRefreshThread()中,往后看就会看到通过Http请求和Erureka服务器交互。服务注册的代码紧接着在服务发现后面
if (clientConfig.shouldRegisterWithEureka()) {
...
// Heartbeat timer
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
// InstanceInfo replicator
instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
clientConfig.getInstanceInfoReplicationIntervalSeconds(),
2); // burstSize
...
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
}
instanceInfoReplicator对当前服务信息( instanceInfo)进行注册。
public void run() {
...
discoveryClient.refreshInstanceInfo();
Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
if (dirtyTimestamp != null) {
discoveryClient.register();
instanceInfo.unsetIsDirty(dirtyTimestamp);
}
...
}
另外一个心跳检测的定时任务则是对服务进行续约
boolean renew() {
EurekaHttpResponse<InstanceInfo> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
if (httpResponse.getStatusCode() == 404) {
...
return register();
}
return httpResponse.getStatusCode() == 200;
} catch (Throwable e) {
logger.error("{} - was unable to send heartbeat!", PREFIX + appPathIdentifier, e);
return false;
}
}
可以看出如果续约失败则重新发起一次注册。那么服务注册的具体实现就在register()中
boolean register() throws Throwable {
EurekaHttpResponse<Void> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
} catch (Exception e) {
logger.warn("{} - registration failed {}", PREFIX + appPathIdentifier, e.getMessage(), e);
throw e;
}
if (logger.isInfoEnabled()) {
logger.info("{} - registration status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
}
return httpResponse.getStatusCode() == 204;
}
实现十分简单,就是发起一次http调用,把当前instanceInfo传递过去。,那么InstanceInfo都有什么呢,其实就是一些appName ,url,port等等,通过这些信息我们就能发起一次基于http的rpc调用。
小结
可以看出,Spring cloud中的服务发现和注册其实就是和通过http方式和eureka进行通信,实现手段则是通过定时任务进行定时操作,包括定时查询,服务续约。