Spring Cloud Eureka(二):Client源码
入口
在上一篇搭建的 Spring Cloud Client 客户端项目中,只是在配置文件中加了一点配置,Eureka 客户端就成功启动了,那么客户端是如何加入 Spring 的呢?
我们知道如果想注册一些Bean到Spring中,但是这些Bean又不在Spring的扫描路径下,那就只剩下两种方案:
- 使用 @Configuration 或 @Import
- 使用
SpringBoot SPI
机制,配置到spring.factories
文件中
找到 spring-cloud-netflix-eureka-client.jar 中 META-INF/spring.factories 文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.reactive.EurekaReactiveDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.loadbalancer.LoadBalancerEurekaAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaConfigServerBootstrapConfiguration
这些配置类注册了一系列 Eureka 需要的 bean,这也是我们寻找一些重要的类的入手处。
EurekaDiscoveryClient
客户端最重要的就是从服务端发现服务,Spring Cloud 定义了一个用于服务发现的顶级接口org.springframework.cloud.client.discovery.DiscoveryClient
// 通用服务发现的操作
public interface DiscoveryClient extends Ordered {
// 省略 Ordered 接口方法...
// 实现类的描述
String description();
// 通过服务id查询服务实例信息列表
List<ServiceInstance> getInstances(String serviceId);
// 获取所有服务实例id
List<String> getServices();
}
Eureka 也有对应的实现类,在spring.factories
文件中的配置类EurekaDiscoveryClientConfiguration
里注册
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@ConditionalOnBlockingDiscoveryEnabled
public class EurekaDiscoveryClientConfiguration {
// 省略其他配置...
@Bean
@ConditionalOnMissingBean
public EurekaDiscoveryClient discoveryClient(EurekaClient client,
EurekaClientConfig clientConfig) {
return new EurekaDiscoveryClient(client, clientConfig);
}
}
注册该类需要另外两个 bean EurekaClient
和EurekaClientConfig
EurekaClientConfig
// 接口,指定了一个默认的实现
@ImplementedBy(DefaultEurekaClientConfig.class)
public interface EurekaClientConfig {...}
// Eureka 自己的实现类,将配置文件中的配置项保存在这个类
@ConfigurationProperties(EurekaClientConfigBean.PREFIX)
public class EurekaClientConfigBean implements EurekaClientConfig, Ordered {
// 配置文件中配置项的前缀: eureka.client
public static final String PREFIX = "eureka.client";
// 省略其他...
}
该类在spring.factories
文件中EurekaClientAutoConfiguration
里注册
// 省略类上注解
public class EurekaClientAutoConfiguration {
// 省略其他配置...
@Bean
@ConditionalOnMissingBean(value = EurekaClientConfig.class,
search = SearchStrategy.CURRENT)
public EurekaClientConfigBean eurekaClientConfigBean(ConfigurableEnvironment env) {
return new EurekaClientConfigBean();
}
}
EurekaClient
EurekaClient最底层的实现类是CloudEurekaClient
,该类只是在方法onCacheRefreshed被调用时发送一个心跳事件,主要的工作还是com.netflix.discovery.DiscoveryClient
完成。
public class DiscoveryClient implements EurekaClient {
// 省略其他方法及方法体
// 服务注册
boolean register();
// 服务续约
boolean renew();
// 服务下线,来自 EurekaClient 接口
synchronized void shutdown();
// 以下来自 LookupService 接口
// 查询相同appName的服务实例信息
Application getApplication(String appName);
// 查找所有服务实例信息
Applications getApplications();
// 根据实例id获取实例信息
List<InstanceInfo> getInstancesById(String id);
}
下面重点来看DiscoveryClient
DiscoveryClient
先看构造函数
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
// 一些初始化工作,省略
// 配置项 eureka.client.region
logger.info("Initializing Eureka in region {}", clientConfig.getRegion());
// 不拉取 && 不注册
if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
// 不启动任何任务, DiscoveryClient 初始化完成
// 省略代码...
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()
); // use direct handoff
// 缓存刷新
cacheRefreshExecutor = new ThreadPoolExecutor(
1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
.setDaemon(true)
.build()
);
// 省略部分代码...
}
// 拉取注册表
if (clientConfig.shouldFetchRegistry()) {
try {
// 主服务器拉取,false:增量拉取
boolean primaryFetchRegistryResult = fetchRegistry(false);
if (!primaryFetchRegistryResult) {
logger.info("Initial registry fetch from primary servers failed");
}
// 主服务器拉取失败,从备份服务器拉取
boolean backupFetchRegistryResult = true;
if (!primaryFetchRegistryResult && !fetchRegistryFromBackup()) {
backupFetchRegistryResult = false;
logger.info("Initial registry fetch from backup servers failed");
}
// 两次都失败 && 初始化时强制拉取注册表,则初始化失败
// 配置项:eureka.cletn.should-enforce-registration-at-init
if (!primaryFetchRegistryResult && !backupFetchRegistryResult && clientConfig.shouldEnforceFetchRegistryAtInit()) {
throw new IllegalStateException("Fetch registry error at startup. Initial fetch failed.");
}
} catch (Throwable th) {
logger.error("Fetch registry error at startup: {}", th.getMessage());
throw new IllegalStateException(th);
}
}
// 注册到 Eureka Server && 初始化时强制注册
if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {
try {
// 向 Eureka Server 注册自己
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);
}
}
// 初始化定时任务:服务心跳,服务拉取,服务信息实例发送
initScheduledTasks();
// 省略部分代码...
}
构造函数小结:
- 初始化部分信息
- 拉取注册表
- 注册自己
- 初始化定时器:服务心跳,服务拉取,服务实例信息发送
fentchRegistry:拉取注册表
private boolean fetchRegistry(boolean forceFullRegistryFetch) {
Stopwatch tracer = FETCH_REGISTRY_TIMER.start();
try {
// 获取现有的注册表
Applications applications = getApplications();
if (clientConfig.shouldDisableDelta() // 增量获取禁用 eureka.clent.disable-delta,默认false
// 配置了 eureka.client.registry-refresh-single-vip-address
|| (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))
|| forceFullRegistryFetch
// 现有注册表为null
|| (applications == null)
// 现有注册表不为null但是为空
|| (applications.getRegisteredApplications().size() == 0)
// 客户端不支持增量拉取
|| (applications.getVersion() == -1))
{
// 全量获取
getAndStoreFullRegistry();
} else {
// 增量获取
getAndUpdateDelta(applications);
}
applications.setAppsHashCode(applications.getReconcileHashCode());
logTotalInstances();
} catch (Throwable e) {
logger.info(PREFIX + "{} - was unable to refresh its cache! This periodic background refresh will be retried in {} seconds. status = {} stacktrace = {}",
appPathIdentifier, clientConfig.getRegistryFetchIntervalSeconds(), e.getMessage(), ExceptionUtils.getStackTrace(e));
return false;
} finally {
if (tracer != null) {
tracer.stop();
}
}
// 发送注册表刷新事件
onCacheRefreshed();
// 更新此实例的状态到服务端
updateInstanceRemoteStatus();
// 拉取成功
return true;
}
@Override
public Applications getApplications() {
return localRegionApps.get();
}
getAndStoreFullRegistry:全量注册表
// 注册表版本计数器,防止多线程环境下注册表更新为旧版本
private final AtomicLong fetchRegistryGeneration;
private void getAndStoreFullRegistry() throws Throwable {
// 注册表当前版本
long currentUpdateGeneration = fetchRegistryGeneration.get();
logger.info("Getting all instance registry info from the eureka server");
Applications apps = null;
// eurekaTransport.queryClient: AbstractJerseyEurekaHttpClient
// 配置了 eureka.client.registry-refresh-single-vip-address,则从该配置地址取
// remoteRegionsRef.get() 取的是 eureka.client.fetch-remote-regions-registry 配置项
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)) {
// 拉取到注册表,且当前版本一致,则版本+1
// 过滤(只保留状态为 UP 的)并 打乱注册表
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");
}
}
AbstractJerseyEurekaHttpClient
public abstract class AbstractJerseyEurekaHttpClient implements EurekaHttpClient {
@Override
public EurekaHttpResponse<Applications> getApplications(String... regions) {
return getApplicationsInternal("apps/", regions);
}
@Override
public EurekaHttpResponse<Applications> getVip(String vipAddress, String... regions) {
return getApplicationsInternal("vips/" + vipAddress, regions);
}
@Override
public EurekaHttpResponse<Applications> getDelta(String... regions) {
return getApplicationsInternal("apps/delta", regions);
}
private EurekaHttpResponse<Applications> getApplicationsInternal(String urlPath, String[] regions) {
// 简化代码
// 发送 GET 请求,Jersey HTTP GET serviceUrl/urlPath?regions=
}
}
getAndUpdateDelta:增量注册表
private final Lock fetchRegistryUpdateLock = new ReentrantLock();
private void getAndUpdateDelta(Applications applications) throws Throwable {
// 当前版本
long currentUpdateGeneration = fetchRegistryGeneration.get();
// 发送 HTTP 请求获取增量信息
Applications delta = null;
EurekaHttpResponse<Applications> httpResponse = eurekaTransport.queryClient.getDelta(remoteRegionsRef.get());
if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
delta = httpResponse.getEntity();
}
if (delta == null) {
// 服务端不允许增量拉取,则全量拉取
getAndStoreFullRegistry();
} else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) {
// 拉取到注册表,且当前版本一致,则版本+1
logger.debug("Got delta update with apps hashcode {}", delta.getAppsHashCode());
String reconcileHashCode = "";
if (fetchRegistryUpdateLock.tryLock()) {
// 拿到锁
try {
// 更新增量
updateDelta(delta);
reconcileHashCode = getReconcileHashCode(applications);
} finally {
// 释放锁
fetchRegistryUpdateLock.unlock();
}
} else {
// 没拿到锁则放弃此次更新
logger.warn("Cannot acquire update lock, aborting getAndUpdateDelta");
}
if (!reconcileHashCode.equals(delta.getAppsHashCode()) || clientConfig.shouldLogDeltaDiff()) {
// 由于一些原因导致客户端和服务端注册信息不一致,则再次全量拉取
reconcileAndLogDifference(delta, reconcileHashCode);
}
} else {
// 有其他线程已经更新了且版本更高
logger.warn("Not updating application delta as another thread is updating it already");
logger.debug("Ignoring delta update with apps hashcode {}, as another thread is updating it already", delta.getAppsHashCode());
}
}
private void updateDelta(Applications delta) {
for (Application app : delta.getRegisteredApplications()) {
for (InstanceInfo instance : app.getInstances()) {
Applications applications = getApplications();
// 省略部分代码...
// 新增的服务实例
if (ActionType.ADDED.equals(instance.getActionType())) {
Application existingApp = applications.getRegisteredApplications(instance.getAppName());
if (existingApp == null) {
applications.addApplication(app);
}
logger.debug("Added instance {} to the existing apps in region {}", instance.getId(), instanceRegion);
applications.getRegisteredApplications(instance.getAppName()).addInstance(instance);
}
// 变更的服务实例
else if (ActionType.MODIFIED.equals(instance.getActionType())) {
Application existingApp = applications.getRegisteredApplications(instance.getAppName());
if (existingApp == null) {
applications.addApplication(app);
}
logger.debug("Modified instance {} to the existing apps ", instance.getId());
applications.getRegisteredApplications(instance.getAppName()).addInstance(instance);
}
// 删除的服务实例
else if (ActionType.DELETED.equals(instance.getActionType())) {
Application existingApp = applications.getRegisteredApplications(instance.getAppName());
if (existingApp != null) {
logger.debug("Deleted instance {} to the existing apps ", instance.getId());
existingApp.removeInstance(instance);
// 如果实例列表为空则删除该服务
if (existingApp.getInstancesAsIsFromEureka().isEmpty()) {
applications.removeApplication(existingApp);
}
}
}
}
}
// 省略代码,设置版本,打乱实例顺序
}
register:注册自己
boolean register() throws Throwable {
// 简化代码
// instanceInfo 当前实例信息
eurekaTransport.registrationClient.register(instanceInfo);
}
public abstract class AbstractJerseyEurekaHttpClient implements EurekaHttpClient {
@Override
public EurekaHttpResponse<Void> register(InstanceInfo info) {
// 简化代码
String urlPath = "apps/" + info.getAppName();
// 发送请求 Jersey HTTP POST
// url: serviceUrl/urlPath
// body: info, contentType: application/json
}
}
initScheduledTasks:初始化定时器
private void initScheduledTasks() {
if (clientConfig.shouldFetchRegistry()) {
// 注册表拉取定时器
// 拉取注册表的频率, eureka.client.registry-fetch-interval-seconds,默认30s
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
cacheRefreshTask = new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
// 拉取注册表线程
new CacheRefreshThread()
);
scheduler.schedule(cacheRefreshTask, registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
if (clientConfig.shouldRegisterWithEureka()) {
// 心跳定时器
// 心跳发送间隔,eureka.instance.lease-renewal-interval-in-seconds: 默认30s
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);
heartbeatTask = new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
// 心跳线程
new HeartbeatThread()
);
scheduler.schedule(heartbeatTask,renewalIntervalInSecs, TimeUnit.SECONDS);
// 服务实例信息刷新,有变化则向 server 注册
instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
// eureka.client.instance-info-replication-interval-seconds,默认30s
clientConfig.getInstanceInfoReplicationIntervalSeconds(),
2);
// 状态监听,状态改变则触发 instanceInfoReplicator 执行
statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
@Override
public String getId() {
return "statusChangeListener";
}
@Override
public void notify(StatusChangeEvent statusChangeEvent) {
if (statusChangeEvent.getStatus() == InstanceStatus.DOWN) {
logger.error("Saw local status change event {}", statusChangeEvent);
} else {
logger.info("Saw local status change event {}", statusChangeEvent);
}
instanceInfoReplicator.onDemandUpdate();
}
};
// eureka.client.on-demand-update-status-change,默认true
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
} else {
logger.info("Not registering with Eureka server per configuration");
}
}
CacheRefreshThread:拉取注册表线程
// DiscoveryClient 内部类
class CacheRefreshThread implements Runnable {
public void run() {
refreshRegistry();
}
}
@VisibleForTesting
void refreshRegistry() {
try {
// 省略代码,region处理
// 拉取注册表
boolean success = fetchRegistry(remoteRegionsModified);
} catch (Throwable e) {
logger.error("Cannot fetch registry from server", e);
}
}
HeartbeatThread:心跳线程
// DiscoveryClient 内部类
private class HeartbeatThread implements Runnable {
public void run() {
if (renew()) {
lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
}
}
}
// 续约
boolean renew() {
// 简化部分代码
EurekaHttpResponse<InstanceInfo> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
// 服务端返回404,表示没有此实例,则重新发起注册
if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
long timestamp = instanceInfo.setIsDirtyWithTime();
// 重新注册自身
boolean success = register();
return success;
}
return httpResponse.getStatusCode() == Status.OK.getStatusCode();
} catch (Throwable e) {
logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
return false;
}
}
// AbstractJerseyEurekaHttpClient#sendHeartBeat
public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
// 简化代码
String urlPath = "apps/" + appName + '/' + id;
// 发送请求 Jersey HTTP PUT
// url: serviceUrl/urlPath
// query param: status = info.getStatus()
// query param: lastDirtyTimestamp = info.getLastDirtyTimestamp()
}
InstanceInfoReplicator:服务实例信息刷新线程
class InstanceInfoReplicator implements Runnable {
public void run() {
try {
// 刷新服务实例信息
discoveryClient.refreshInstanceInfo();
// 实例信息有变化,返回数据更新时间
Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
if (dirtyTimestamp != null) {
// 重新发起注册
discoveryClient.register();
instanceInfo.unsetIsDirty(dirtyTimestamp);
}
} catch (Throwable t) {
logger.warn("There was a problem with the instance info replicator", t);
} finally {
// 休眠 replicationIntervalSeconds 秒 再次执行
Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}
}
定时器小结
- 拉取注册表,频率由
eureka.client.registry-fetch-interval-seconds
决定,默认30s - 心跳续约,频率由
eureka.instance.lease-renewal-interval-in-seconds
决定,默认30s - 服务实例信息刷新,频率由
eureka.client.instance-info-replication-interval-seconds
决定,默认30s - 状态监听器,监听状态变化的事件,触发服务实例信息刷新执行,属于服务实例信息刷新的被动表现,是否启用由
eureka.client.on-demand-update-status-change
决定,默认true
shutdown:服务下线
@PreDestroy
@Override
public synchronized void shutdown() {
if (isShutdown.compareAndSet(false, true)) {
logger.info("Shutting down DiscoveryClient ...");
// 删除状态监听
if (statusChangeListener != null && applicationInfoManager != null) {
applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId());
}
// 关闭定时任务
cancelScheduledTasks();
if (applicationInfoManager != null
&& clientConfig.shouldRegisterWithEureka()
&& clientConfig.shouldUnregisterOnShutdown()) {
// 给服务端发送下线状态
applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
unregister();
}
// 省略代码,关闭一些东西
logger.info("Completed shut down of DiscoveryClient");
}
}
void unregister() {
// 简化代码
eurekaTransport.registrationClient.cancel(instanceInfo.getAppName(), instanceInfo.getId());
}
public abstract class AbstractJerseyEurekaHttpClient implements EurekaHttpClient {
@Override
public EurekaHttpResponse<Void> cancel(String appName, String id) {
// 简化代码
String urlPath = "apps/" + appName + '/' + id;
// 发送请求 Jersey HTTP DELETE
// url: serviceUrl/urlPath
}
}
总结
这一篇从DiscoveryClient
的构造函数开始,浏览了一遍 Eureka 客户端的工作流程,包括拉取注册表,注册自身,心跳续约等任务。也看到了一些配置项生效的源码,以及和服务端通信的 HTTP 接口路径。看完了这些源码,对 Eureka 客户端的实现原理也有了一定的认识。如果有兴趣,完全可以自己去实现一个其他语言的客户端。