Eureka Client与应用程序相结合,提供了服务注册、续期、获取注册表等功能。 相比于Eureka Server,因为它是无状态的,因此实现上也简单很多。
我们一起来看看,源码中Eureka Client是如何启动的?
一 核心类DiscoveryClient
来看源码中注释:
该类是与Eureka Server交互的工具。Eureka客户端负责:
a)在Eureka服务器上注册实例
b)与Eureka服务器续签租约
c)在关闭期间从Eureka服务器取消租约
d)查询在Eureka Server上注册的服务/实例列表
Eureka Client需要一个配置好的Eureka Server url列表 来与之对话。这些java.net.url通常是不会更改的amazon弹性ip。在与某个Eureka Server交互失败时,都将故障转移到列表中其他的Eureka Server url。
如下Eureka Client的配置,Eureka Server url就是指serviceUrl,当其中某个地址不通时,将使用其他url,即故障转移。
yaml
# eureka client配置示例
eureka:
client:
fetch-registry: true
register-with-eureka: true
serviceUrl:
# 当Eureka Server集群部署时,此处应配置多个url
defaultZone: http://192.168.2.4:8761/eureka/,http://192.168.2.125:8761/eureka/
在DiscoveryClient的构造方法中,完成了Eureka Client的启动。
二、初始化过程
启动过程,其实就是创建一个DiscoveryClient实例的过程。
1.1 DiscoveryClient的构造方法
- 构造器中,提供了一个创建BackupRegistry子类实例的Provider;
BackupRegistry接口,是在client无法从任何eureka Server获取注册表时,提供一个配置的注册表。默认采用空实现。
- ApplicationInfoManager:管理当前应用节点自身信息,如ipAddr、port、status等,这些信息用于注册,或被其他服务发现;
- EurekaClientConfig:eureka客户端向eureka服务器注册实例所需的配置信息 。大多数必需的信息由默认配置DefaultEurekaClientConfig提供,用户只需要提供eureka服务器服务url。
2.2 创建空注册表
java
private final AtomicReference<Applications> localRegionApps = new AtomicReference<Applications>();
// 创建空应用集
localRegionApps.set(new Applications());
Applications是对eureka server返回的注册表的封装,关键属性有:应用队列、注册表。 而Application用于保存某个应用程序的节点列表,有以下属性。 再来看InstanceInfo
类,是对某个应用集群某个节点的封装 ,关键属性是节点的网络地址:instanceId、appName、ipAddr和port。
2.3 保存serviceUrl和创建Transport
java
// 对serviceUrl的封装
remoteRegionsToFetch = new AtomicReference<String>(clientConfig.fetchRegistryForRemoteRegions());
remoteRegionsRef = new AtomicReference<>(remoteRegionsToFetch.get() == null ? null : remoteRegionsToFetch.get().split(","));
EurekaTransport是DiscoveryClient中的内部类,用于创建和持有与Eureka Server交互的HttpClient:分别创建了用于fetch、pull的Client。
java
eurekaTransport = new EurekaTransport();
scheduleServerEndpointTask(eurekaTransport, args);
TransportClientFactories
有两个实现,默认使用Jersey1TransportClientFactories
2.4 创建线程池
当shouldRegisterWithEureka、shouldFetchRegistry都为false时,会提前return。否则,创建3个线程池。 将在后面看到对它们的使用。
java
// 调用线程池
private final ScheduledExecutorService scheduler;
// 执行注册、心跳请求
private final ThreadPoolExecutor heartbeatExecutor;
// 刷新Eureka client端缓存的注册表
private final ThreadPoolExecutor cacheRefreshExecutor;
2.5 从Eureka Server拉取注册表
java
// 从Eureka Server拉取注册表
if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
// 失败时通过BackupRegistry获取
fetchRegistryFromBackup();
}
注意,源码中"是否拉取注册表",配置名是shouldFetchRegistry
,在springboot整合包中叫fetchRegistry
,默认为true。
来看fetchRegistry(),在第一次获取注册表时为全量拉取;之后只获取增量。
java
// 简化后代码
if (全量拉取){
getAndStoreFullRegistry();
} else {
getAndUpdateDelta(applications);
}
2.6 启动周期任务
调用DiscoveryClient.initScheduledTasks()
,创建了刷新注册表、续租、节点信息更新同步任务。
- 如果shouldFetchRegistry=true,则添加周期刷新注册表任务
java
// 周期刷新注册表
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread()
),
registryFetchIntervalSeconds, TimeUnit.SECONDS);
- 如果shouldRegisterWithEureka为true,则创建心跳周期任务
java
// Heartbeat timer
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
-
注册StatusChangeListener 当配置shouldOnDemandUpdateStatusChange=true时,将在节点状态变为DOWN时,立即同步节点信息给Eureka。
-
启动节点信息更新同步任务 创建
InstanceInfoReplicator
实例,用于更新该节点的InstanceInfo(如元数据、节点状态等),将其同步到Eureka server。- 当节点信息发生变化时,设置InstanceInfo.isInstanceInfoDirty=true和lastDirtyTimestamp;
- 使用仅有1个线程的
ScheduledThreadPool
,来周期检查isInstanceInfoDirty标志 - 节点信息更新同步,是通过发起一次注册来实现;
2.7 启动监控任务
三、总结
- DiscoveryClient是核心资源持有者,包括应用集、注册表、线程池、Http客户端等;
- Eureka Client的启动过程,就是创建DiscoveryClient示例的过程;
- 启动时,初始化一些全局资源,从Eureka初次拉取注册表,启动刷新注册表本地缓存、续租、节点信息变更同步这3个周期任务;
在随后的文章中,将进一步熟悉客户端注册、续租等的实现。