0. 前言
- springboot版本:2.1.9.RELEASE
- springcloud版本:Greenwich.SR4
1. 方法入口
public class EurekaClientAutoConfiguration {
@Configuration
@ConditionalOnRefreshScope
protected static class RefreshableEurekaClientConfiguration {
// ......
@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,
@Autowired(required = false) HealthCheckHandler healthCheckHandler) {
// ......
// Step1:初始化 CloudEurekaClient
CloudEurekaClient cloudEurekaClient = new CloudEurekaClient(appManager,
config, this.optionalArgs, this.context);
cloudEurekaClient.registerHealthCheck(healthCheckHandler);
return cloudEurekaClient;
}
}
// ......
}
// CloudEurekaClient.class
public CloudEurekaClient(ApplicationInfoManager applicationInfoManager,
EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs<?> args,
ApplicationEventPublisher publisher) {
// Step2:调用父类构造方法
super(applicationInfoManager, config, args);
// ......
}
// DiscoveryClient.class
public DiscoveryClient(ApplicationInfoManager applicationInfoManager, final EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args) {
this(...);
}
public DiscoveryClient(ApplicationInfoManager applicationInfoManager, final EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, EndpointRandomizer randomizer) {
this(...);
}
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
// ......
// 2 从服务端拉取注册表信息 fetchRegistry()
if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
// 如果从服务端拉取注册表失败,则获取备用的
// 备用注册表需要我们自己实现,并通过配置文件配置后才能使用
// 备用注册表实现方式:
// 1. 从远程 region 的注册表获取
// 2. 本地返回默认注册表
fetchRegistryFromBackup();
}
// ......
// 3 初始化定时任务,其中包含拉取注册表信息的定时任务
initScheduledTasks();
// ......
}
2. fetchRegistry()
// DiscoveryClient.class
private boolean fetchRegistry(boolean forceFullRegistryFetch) {
Stopwatch tracer = FETCH_REGISTRY_TIMER.start();
try {
// If the delta is disabled or if it is the first time, get all
// applications
Applications applications = getApplications();
if (clientConfig.shouldDisableDelta()
|| (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))
|| forceFullRegistryFetch
|| (applications == null)
|| (applications.getRegisteredApplications().size() == 0)
|| (applications.getVersion() == -1)) //Client application does not have latest library supporting delta
{
// ......
// 2.1 全量拉取注册表信息
getAndStoreFullRegistry();
} else {
// 2.2 增量拉取注册表信息
getAndUpdateDelta(applications);
}
// ......
} catch (Throwable e) {
// ......
} finally {
// ......
}
// ......
}
2.1 全量拉取注册表信息
// DiscoveryClient.class
private void getAndStoreFullRegistry() throws Throwable {
long currentUpdateGeneration = fetchRegistryGeneration.get();
logger.info("Getting all instance registry info from the eureka server");
Applications apps = null;
// Jersey 请求获取服务端注册表信息
// 配置文件中未配置 registry-refresh-single-vip-address ,默认为 null ,所以调用 getApplications() 方法
EurekaHttpResponse<Applications> httpResponse = clientConfig.getRegistryRefreshSingleVipAddress() == null
? eurekaTransport.queryClient.getApplications(remoteRegionsRef.get())
: eurekaTransport.queryClient.getVip(clientConfig.getRegistryRefreshSingleVipAddress(), remoteRegionsRef.get());
if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
apps = httpResponse.getEntity();
}
logger.info("The response status is {}", httpResponse.getStatusCode());
if (apps == null) {
logger.error("The application is null for some reason. Not storing this information");
} else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) {
// 服务端返回注册表信息过滤乱序后放入本地 localRegionApps
// 保留到本地注册表的实例条件:
// 1. 实例状态为 UP (默认)
// 2. 和当前客户端实例一样的 region (分区)
localRegionApps.set(this.filterAndShuffle(apps));
logger.debug("Got full registry with apps hashcode {}", apps.getAppsHashCode());
} else {
logger.warn("Not updating applications as another thread is updating it already");
}
}
2.2 增量拉取注册表信息
《EurekaClient-启动时初始化的定时任务》中讲到过
3. initScheduledTasks()
// DiscoveryClient.class
private 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,
// 3.1 任务线程
new CacheRefreshThread()
),
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
// ......
}
3.1 CacheRefreshThread()
// DiscoveryClient.class
class CacheRefreshThread implements Runnable {
public void run() {
// 刷新注册表
refreshRegistry();
}
}
@VisibleForTesting
void refreshRegistry() {
try {
// ......
boolean remoteRegionsModified = false;
// ......
// 2 从服务端拉取注册表信息
boolean success = fetchRegistry(remoteRegionsModified);
// ......
} catch (Throwable e) {
logger.error("Cannot fetch registry from server", e);
}
}
4. 总结
- 拉起注册表的入口:客户端启动时、定时刷新注册表任务
- 拉取注册表的方式:全量、增量