在 "上篇文章" 中,我們簡單介紹了EurekaServer自動裝配及啟動流程解析,本篇文章則繼續研究EurekaClient的相關代碼 老規矩,先看 文件,其中引入了一個配置類 上方兩個註解則是這個配置類是否能夠開啟的條件,這裡就不再展開,直接看它引入的配置類吧 1. 細心的讀者可能會發現這裡又註 ...
在上篇文章中,我們簡單介紹了EurekaServer自動裝配及啟動流程解析,本篇文章則繼續研究EurekaClient的相關代碼
老規矩,先看spring.factories
文件,其中引入了一個配置類EurekaDiscoveryClientConfigServiceBootstrapConfiguration
@ConditionalOnClass(ConfigServicePropertySourceLocator.class)
@ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = false)
@Configuration
@Import({ EurekaDiscoveryClientConfiguration.class,
EurekaClientAutoConfiguration.class })
public class EurekaDiscoveryClientConfigServiceBootstrapConfiguration {
}
上方兩個註解則是這個配置類是否能夠開啟的條件,這裡就不再展開,直接看它引入的配置類吧
EurekaDiscoveryClientConfiguration
- 細心的讀者可能會發現這裡又註冊了一個
Marker
類,可以猜測也是某個地方的開關 EurekaClientConfigurationRefresher
這個類看名字就知道這是當配置被動態刷新時的一個處理器,這裡也不再展開了EurekaHealthCheckHandlerConfiguration
這裡面註冊了一個Eureka健康檢查的處理類,這個健康檢查相關的原理分析可以參考這篇文章:SpringBoot健康檢查實現原理
EurekaClientAutoConfiguration
這個類裡面全是重點,也是我們本文的核心
註解
@Configuration
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@Import(DiscoveryClientOptionalArgsConfiguration.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",
"org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
"org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration"})
首先可以看到這個類一共包含這些註解,我們來一一解析比較重要的幾個註解吧
@Import(DiscoveryClientOptionalArgsConfiguration.class)
引入了兩個bean,RestTemplateDiscoveryClientOptionalArgs
和MutableDiscoveryClientOptionalArgs
,這兩個類的作用暫且不說
@ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)
剛纔說的Marker
類的作用出來了
@AutoConfigureBefore
既然必須在這三個類完成自動裝配之後才能進行裝配,那就代表著這三個類肯定大有用途,研究一下吧
NoopDiscoveryClientAutoConfiguration
故名思意,負責服務發現的類,咱們重點關註一下其中的幾個方法
init
@PostConstruct
public void init() {
String host = "localhost";
try {
host = InetAddress.getLocalHost().getHostName();
}
catch (UnknownHostException e) {
log.warn("Cannot get host info: (" + e.getMessage() + ")");
}
int port = findPort();
this.serviceInstance = new DefaultServiceInstance(
this.environment.getProperty("spring.application.name", "application"),
host, port, false);
}
這裡構造了一個DefaultServiceInstance
對象,這個對象包含了當前項目的ip+埠+項目名稱
- 註入bean
NoopDiscoveryClient
@Bean
public DiscoveryClient discoveryClient() {
return new NoopDiscoveryClient(this.serviceInstance);
}
再深入看一下這個類
public class NoopDiscoveryClient implements DiscoveryClient {
public NoopDiscoveryClient(ServiceInstance instance) {
}
@Override
public String description() {
return "Spring Cloud No-op DiscoveryClient";
}
@Override
public List<ServiceInstance> getInstances(String serviceId) {
return Collections.emptyList();
}
@Override
public List<String> getServices() {
return Collections.emptyList();
}
}
這個類包含了獲取當前實例以及當前服務的方法,但是返回的都是空,那麼是不是會在後面的某個地方被覆蓋呢?
CommonsClientAutoConfiguration
進去深入瞭解一下,哎喲,註冊了幾個bean:DiscoveryClientHealthIndicator
、DiscoveryCompositeHealthIndicator
。原來是健康檢查相關的東西,那就忽略了
ServiceRegistryAutoConfiguration
這個配置類中主要註冊了一個bean:ServiceRegistryEndpoint
這個類主要是對外提供對與Eureka狀態的檢查
@ReadOperation
public ResponseEntity getStatus() {
if (this.registration == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("no registration found");
}
return ResponseEntity.ok().body(this.serviceRegistry.getStatus(this.registration));
}
而Eureka的狀態則是通過serviceRegistry
對象獲取的,這個對象會再下方詳細分析
註冊bean
接著來看這個類註入的幾個bean
EurekaClientConfigBean
@Bean
@ConditionalOnMissingBean(value = EurekaClientConfig.class, search = SearchStrategy.CURRENT)
public EurekaClientConfigBean eurekaClientConfigBean(ConfigurableEnvironment env) {
EurekaClientConfigBean client = new EurekaClientConfigBean();
if ("bootstrap".equals(this.env.getProperty("spring.config.name"))) {
client.setRegisterWithEureka(false);
}
return client;
}
這個bean中包含了eureka.client.xxx
系列的一些配置,詳細的配置信息可以參考這裡:https://github.com/shiyujun/syj-study-demo/blob/master/src/main/java/cn/shiyujun/EurekaConfig.md
EurekaInstanceConfigBean
這個bean中主要是包含eureka實例(eureka.instance.xxx
系列)的一些配置信息,詳細的配置信息同上
RefreshableEurekaClientConfiguration.DiscoveryClient
@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);
}
其中CloudEurekaClient
是DiscoveryClient
的子類,而DiscoveryClient
則是EurekaClient的核心類
public CloudEurekaClient(ApplicationInfoManager applicationInfoManager,
EurekaClientConfig config,
AbstractDiscoveryClientOptionalArgs<?> args,
ApplicationEventPublisher publisher) {
//這裡會調用父類DiscoveryClient的構造方法
super(applicationInfoManager, config, args);
this.applicationInfoManager = applicationInfoManager;
this.publisher = publisher;
this.eurekaTransportField = ReflectionUtils.findField(DiscoveryClient.class, "eurekaTransport");
ReflectionUtils.makeAccessible(this.eurekaTransportField);
}
父類的構造方法中執行的代碼塊比較長,一些賦值操作等就忽略了,這裡只摘出比較重要的部分
- 初始化拉取監控和心跳監控
if (config.shouldFetchRegistry()) {
this.registryStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRY_PREFIX + "lastUpdateSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
} else {
this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
}
if (config.shouldRegisterWithEureka()) {
this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRATION_PREFIX + "lastHeartbeatSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
} else {
this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
}
- 噹噹前實例不需要註冊到EurekaServer時,構造方法走到這裡就結束了
if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
logger.info("Client configured to neither register nor query for data.");
scheduler = null;
heartbeatExecutor = null;
cacheRefreshExecutor = null;
eurekaTransport = null;
instanceRegionChecker = new InstanceRegionChecker(new PropertyBasedAzToRegionMapper(config), clientConfig.getRegion());
DiscoveryManager.getInstance().setDiscoveryClient(this);
DiscoveryManager.getInstance().setEurekaClientConfig(config);
initTimestampMs = System.currentTimeMillis();
logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
initTimestampMs, this.getApplications().size());
return;
}
- 初始化心跳線程和刷新線程以及它們的調度器
try {
scheduler = Executors.newScheduledThreadPool(2,
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-%d")
.setDaemon(true)
.build());
heartbeatExecutor = new ThreadPoolExecutor(
1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
.setDaemon(true)
.build()
);
cacheRefreshExecutor = new ThreadPoolExecutor(
1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
.setDaemon(true)
.build()
);
- 從EurekaServer拉取註冊信息
if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
fetchRegistryFromBackup();
}
這裡fetchRegistry
是第一次拉取註冊信息,如果拉取不成功的話則執行fetchRegistryFromBackup
從備份註冊中心獲取,同樣,拉取的信息會放在之後的文章中
- 註冊之前的擴展點
if (this.preRegistrationHandler != null) {
this.preRegistrationHandler.beforeRegistration();
}
這裡是個空的實現,可以通過實現PreRegistrationHandler
介面做些什麼操作
- 向EurekaServer發起註冊
if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {
try {
if (!register() ) {
throw new IllegalStateException("Registration error at startup. Invalid server response.");
}
} catch (Throwable th) {
logger.error("Registration error at startup: {}", th.getMessage());
throw new IllegalStateException(th);
}
}
註冊方法為register
,同樣這裡先不展開
- 初始化幾個定時任務
initScheduledTasks();
private void initScheduledTasks() {
// 從 EurekaServer 拉取註冊信息
if (clientConfig.shouldFetchRegistry()) {
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread()
),
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
// 向 EurekaServer 發送續租心跳
if (clientConfig.shouldRegisterWithEureka()) {
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs);
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
clientConfig.getInstanceInfoReplicationIntervalSeconds(),
2);
statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
@Override
public String getId() {
return "statusChangeListener";
}
@Override
public void notify(StatusChangeEvent statusChangeEvent) {
if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
logger.warn("Saw local status change event {}", statusChangeEvent);
} else {
logger.info("Saw local status change event {}", statusChangeEvent);
}
instanceInfoReplicator.onDemandUpdate();
}
};
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
//註冊狀態監聽器
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
} else {
logger.info("Not registering with Eureka server per configuration");
}
}
至此,EurekaClient的自動裝配與啟動流程就解析完畢了
本文由博客一文多發平臺 OpenWrite 發佈!