本篇主要分享我是如何从0开始读源码 Eureka源码(客户端,服务端的没时间再看了),之前我在读源码时候常会面临:枯燥、烧脑、非常的耗时、找不到切入点不知道从哪开始。然后…就再也没然后了。
但是有些痛苦该来的还是要来的,在面临一些实际问题时,百度也已经救不了,你又会怎么做?想办法绕过去?如果绕不过去呢?换掉解决方案?因为一个bug换掉解决方案,这显然就是扯蛋嘛。这种情况我之前遇到了,而且还不止一次,所以我不得去跟源码,看看里面到底发生了什么。
痛苦的同时你也在成长,在几次的这种情况之后,我总结出了一些怎么看源码的技巧了,所以在这里分享一下。
最近突然感兴趣 Eureka 源码,所以就拿 Eureka Client 看时候做例子了,在读源码前我没有看过任何源码的资料,就只是从 《Spring Cloud 微服务架构开发实战》里面的的 “4.7 深入Eureka” 看到一些理论部分。所以难免有存在理解不到位的地方,可以留言指出,共勉!
面对 Eureka 这么大体量的项目,我们该从哪开始去认识呢?别慌!其实很多时候我们可以从启动日志中看出一点门道(一般可以从2个切入点去开始,一个启动日志,一个调用的地方。像 Ribbon 启动日志里面没有什么主要信息,我就是从调用的地方去延伸的)。就像下面这个日志 Eureka 客户端为例:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.6.RELEASE)
2020-04-06 21:31:14.414 INFO 8688 --- [ main] com.jiao.OrderServiceApplication : No active profile set, falling back to default profiles: default
2020-04-06 21:31:15.013 INFO 8688 --- [ main] o.s.cloud.context.scope.GenericScope : BeanFactory id=bcad0ac4-3d69-31b0-9cc0-e4bf9f111e5f
2020-04-06 21:31:15.801 INFO 8688 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8781 (http)
2020-04-06 21:31:15.812 INFO 8688 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-04-06 21:31:15.812 INFO 8688 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.33]
2020-04-06 21:31:15.931 INFO 8688 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-04-06 21:31:15.931 INFO 8688 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1501 ms
2020-04-06 21:31:16.021 WARN 8688 --- [ main] c.n.c.sources.URLConfigurationSource : No URLs will be polled as dynamic configuration sources.
2020-04-06 21:31:16.021 INFO 8688 --- [ main] c.n.c.sources.URLConfigurationSource : To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
2020-04-06 21:31:16.025 WARN 8688 --- [ main] c.n.c.sources.URLConfigurationSource : No URLs will be polled as dynamic configuration sources.
2020-04-06 21:31:16.026 INFO 8688 --- [ main] c.n.c.sources.URLConfigurationSource : To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
2020-04-06 21:31:16.152 INFO 8688 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-04-06 21:31:18.276 WARN 8688 --- [ main] ockingLoadBalancerClientRibbonWarnLogger : You already have RibbonLoadBalancerClient on your classpath. It will be used by default. As Spring Cloud Ribbon is in maintenance mode. We recommend switching to BlockingLoadBalancerClient instead. In order to use it, set the value of `spring.cloud.loadbalancer.ribbon.enabled` to `false` or remove spring-cloud-starter-netflix-ribbon from your project.
2020-04-06 21:31:18.328 INFO 8688 --- [ main] o.s.c.n.eureka.InstanceInfoFactory : Setting initial instance status as: STARTING
2020-04-06 21:31:18.403 INFO 8688 --- [ main] com.netflix.discovery.DiscoveryClient : Initializing Eureka in region us-east-1
2020-04-06 21:31:19.162 INFO 8688 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using JSON encoding codec LegacyJacksonJson
2020-04-06 21:31:19.162 INFO 8688 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using JSON decoding codec LegacyJacksonJson
2020-04-06 21:31:19.281 INFO 8688 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using XML encoding codec XStreamXml
2020-04-06 21:31:19.281 INFO 8688 --- [ main] c.n.d.provider.DiscoveryJerseyProvider : Using XML decoding codec XStreamXml
2020-04-06 21:31:24.945 INFO 8688 --- [ main] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
2020-04-06 21:31:25.452 INFO 8688 --- [ main] com.netflix.discovery.DiscoveryClient : Disable delta property : false
2020-04-06 21:31:25.452 INFO 8688 --- [ main] com.netflix.discovery.DiscoveryClient : Single vip registry refresh property : null
2020-04-06 21:31:25.452 INFO 8688 --- [ main] com.netflix.discovery.DiscoveryClient : Force full registry fetch : false
2020-04-06 21:31:25.452 INFO 8688 --- [ main] com.netflix.discovery.DiscoveryClient : Application is null : false
2020-04-06 21:31:25.453 INFO 8688 --- [ main] com.netflix.discovery.DiscoveryClient : Registered Applications size is zero : true
2020-04-06 21:31:25.453 INFO 8688 --- [ main] com.netflix.discovery.DiscoveryClient : Application version is -1: true
2020-04-06 21:31:25.453 INFO 8688 --- [ main] com.netflix.discovery.DiscoveryClient : Getting all instance registry info from the eureka server
2020-04-06 21:31:25.626 INFO 8688 --- [ main] com.netflix.discovery.DiscoveryClient : The response status is 200
2020-04-06 21:31:25.628 INFO 8688 --- [ main] com.netflix.discovery.DiscoveryClient : Starting heartbeat executor: renew interval is: 30
2020-04-06 21:31:25.630 INFO 8688 --- [ main] c.n.discovery.InstanceInfoReplicator : InstanceInfoReplicator onDemand update allowed rate per min is 4
2020-04-06 21:31:25.635 INFO 8688 --- [ main] com.netflix.discovery.DiscoveryClient : Discovery Client initialized at timestamp 1586179885634 with initial instances count: 3
2020-04-06 21:31:25.638 INFO 8688 --- [ main] o.s.c.n.e.s.EurekaServiceRegistry : Registering application ORDER-SERVICE with eureka with status UP
2020-04-06 21:31:25.638 INFO 8688 --- [ main] com.netflix.discovery.DiscoveryClient : Saw local status change event StatusChangeEvent [timestamp=1586179885638, current=UP, previous=STARTING]
2020-04-06 21:31:25.641 INFO 8688 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_ORDER-SERVICE/SD-20200309VVHS:order-service:8781: registering service...
2020-04-06 21:31:25.679 INFO 8688 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_ORDER-SERVICE/SD-20200309VVHS:order-service:8781 - registration status: 204
2020-04-06 21:31:25.682 INFO 8688 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8781 (http) with context path ''
2020-04-06 21:31:25.683 INFO 8688 --- [ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8781
2020-04-06 21:31:26.606 INFO 8688 --- [ main] com.jiao.OrderServiceApplication : Started OrderServiceApplication in 15.167 seconds (JVM running for 15.789)
首先分析下你想从日志中获取什么关键字,因为我是要找 Eureka 相关的,而且客户端启动时候会向服务端注册,我就找 “register” 关键字, 搜索发现的确存在几个这样的关键字。然后再根据缩小范围。因为 Eureka 与 netflix 相关。所以一般我们就找第一行或第二行包名代码与 netflix 相关的日志。最终定位到:
2020-04-06 21:31:18.403 INFO 8688 --- [ main] com.netflix.discovery.DiscoveryClient : Initializing Eureka in region us-east-1
这行很明显是初始化 Eureka Client 的,那就进入这个 DiscoveryClient.java 文件搜索这个关键字(直接搜不出来,分析可能存在动态内容,所以就只搜最核心的关键字 Initializing Eureka),然后打个断点,重启项目开始跟踪。
由上得知,进入 DiscoveryClient 的这个构造中。首先分析 启动流程都大概走了哪些步骤:
EurekaClientAutoConfiguration(见名思意,Eureka 自动配置累) > CloudEurekaClient >> DiscoveryClient
最后发现进入 DiscoveryClient 类的某个构造中开始初始化整个 Eureka client。
/**
**去认识一个类时候,从 类名、文档注释、已经到的与调用的地方就能大致了解到这个类是干嘛的。然后通过使用到的地方开始向外延伸**
*/
@Inject
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
Provider backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
/** 1. 大致分析参数各类型作用(实在不懂的不要去深究,这样只会把你拖住,继续进行下去或许跟着跟着就明白是干嘛用的了)
ApplicationInfoManager applicationInfoManager
一个类干嘛用的首先要看类的文档注释,说明如下:
初始化在 Eureka Server 中注册并由其他组件发现的信息的类。注册所需的信息由通过传递 EurekaInstanceConfig 中定义的配置来提供。
通过上面的注释大致明白,ApplicationInfoManager 这个类就是针对这个 Eureka Client 整个的描述,初始化时候需要依赖 EurekaInstanceConfig 中的配置信息,
然后注册到 Eureka server 端。
EurekaClientConfig config > EurekaClientConfigBean
这个类是一个接口,有2个实现,根据断点看到传入的为:EurekaClientConfigBean,看下这个类的说明:Eureka客户端配置Bean,这个很明显不用解释了吧。
既然存配置信息的,那就观察下它主要存哪些信息:
//默认 Eureka Server 地址(也就是说,我们不用指定Eurek Server 信息,默认就是用了 本地localhost:8761?)
static final String DEFAULT_URL = "http://localhost:8761" + DEFAULT_PREFIX;
//表示从 eureka 服务器获取注册表信息的频率(以秒为单位)。之前理论讲一大堆终于找到哪里配置的了
private int registryFetchIntervalSeconds = 30;
。。。 里面N多变量配置,就不看了,因为这不是主要的。大致明白就行
AbstractDiscoveryClientOptionalArgs args:略过
Provider backupRegistryProvider:
通过类注释 与 类名 以及 使用到的地方分析得知:这个类主要用作备份 Eureka Server 中所有的注册表信息,如果所有是 Eureka 服务端的url
都访问不通过,则从这个类实例中获取注册表
EndpointRandomizer endpointRandomizer:略过
*/
/**
2. 这里只需要关系初始化了哪些东西就行了
this.healthCheckHandlerProvider 空就不管了
this.healthCheckCallbackProvider 空就不管了
this.eventListeners.addAll(args.getEventListeners());
CopyOnWriteArraySet eventListeners 使用了一个线程安全容器去存储监听器 EurekaEventListener
主要监听 Eureka 客户端的一些状态:InstanceStatus 是一个枚举
this.preRegistrationHandler = 空就不管了
*/
if (args != null) {
this.healthCheckHandlerProvider = args.healthCheckHandlerProvider;
this.healthCheckCallbackProvider = args.healthCheckCallbackProvider;
this.eventListeners.addAll(args.getEventListeners());
this.preRegistrationHandler = args.preRegistrationHandler;
} else {
this.healthCheckCallbackProvider = null;
this.healthCheckHandlerProvider = null;
this.preRegistrationHandler = null;
}
this.applicationInfoManager = applicationInfoManager;
/**
1. 过类名与属性以及使用到的地方发现这个类就是向Eureka Server端 注册 以及续约 时候发送的实体信息。
从当前类的以下方法发现:
boolean register():注册
boolean renew():续约
void unregister():服务销毁
2. 其中实例中有一个变量 instanceId,在 AppName范围内他是唯一的,意思就是一个服务可以有多个节点,而每个
节点他这个id是唯一的。
我们这里就叫他当前客户端实例信息
3. *** 这三个方法作为延伸点,看它们什么时候被调用、被谁调用、调用时做了什么 ***
*/
InstanceInfo myInfo = applicationInfoManager.getInfo();
clientConfig = config;
staticClientConfig = clientConfig;
transportConfig = config.getTransportConfig();
instanceInfo = myInfo;
if (myInfo != null) {
appPathIdentifier = instanceInfo.getAppName() + "/" + instanceInfo.getId();
} else {
logger.warn("Setting instanceInfo to a passed in null value");
}
//上面已经提到的
this.backupRegistryProvider = backupRegistryProvider;
this.endpointRandomizer = endpointRandomizer;
this.urlRandomizer = new EndpointUtils.InstanceInfoBasedUrlRandomizer(instanceInfo);
/**
1. 从类注释中可以看出这个类,主要存放从 Eureka 服务端获取的注册表信息,用作本地缓存
2. 而内部又有2个变量去保存所有的服务器所有的注册表新
private final AbstractQueue applications;
private final Map appNameApplicationMap;
3. 关键使用到的地方 getAndStoreFullRegistry(),从 Eureka 服务获取注册表信息 。
getAndUpdateDelta() 内部会同步的增量远程更新注册表
4. 上面的2个方法通过搜索发现一个调用流程,倒叙查找调用链为 :
getAndStoreFullRegistry() < getAndUpdateDelta() < fetchRegistry() < refreshRegistry()
< class CacheRefreshThread implements Runnable(线程类) < initScheduledTasks(初始化几个计时任务,像定时刷新注册表缓存、像服务端发送心跳,instanceInfo复制器)
**** 延伸点 *****
*/
localRegionApps.set(new Applications());
//从字面与使用的地方来看,这个原子只有在更新完注册表缓存时候才+1,记录注册表更新了多少的同时
//原子更新 AtomicReference localRegionApps 本地缓存注册表的信息
fetchRegistryGeneration = new AtomicLong(0);
remoteRegionsToFetch = new AtomicReference(clientConfig.fetchRegistryForRemoteRegions());
remoteRegionsRef = new AtomicReference<>(remoteRegionsToFetch.get() == null ? null : remoteRegionsToFetch.get().split(","));
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;
}
logger.info("Initializing Eureka in region {}", clientConfig.getRegion());
/**
shouldRegisterWithEureka():在 EurekaClientConfigBean 中实现
注释意思为:指示此实例是否应在eureka 服务器上注册其信息以供他人发现。
在某些情况下,您不希望发现您的实例,而您只是希望发现其他实例。
在结合 if 代码块下面的代码意思,所以我猜测这个地方应该是区分客户端与服务器端代码走向的。
因为if下面代码都是定时任务去请求服务端的。当前我 debug 的项目是 Eureka Client 没进if里面,
然后我又Debug一下 Eureka Server ,的确进入了if 里面,所以断定这块是区分 服务端 与 客户端的
*/
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());
// This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()
// to work with DI'd DiscoveryClient
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; // no need to setup up an network tasks and we are done
}
try {
/***
以下初始化线程池,如 定时刷新注册表缓存、像服务端发送心跳。
*/
//这个主要用于续约,后面会调用下面的 heartbeatExecutor(心跳发送) 进行续约
// default size of 2 - 1 each for heartbeat and cacheRefresh
scheduler = Executors.newScheduledThreadPool(2,
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-%d")
.setDaemon(true)
.build());
//心跳发送
heartbeatExecutor = new ThreadPoolExecutor(
1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
.setDaemon(true)
.build()
); // use direct handoff
//注册表缓存刷新
cacheRefreshExecutor = new ThreadPoolExecutor(
1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
.setDaemon(true)
.build()
); // use direct handoff
eurekaTransport = new EurekaTransport();
//初始化一些工厂 与 http连接服务端的类
scheduleServerEndpointTask(eurekaTransport, args);
AzToRegionMapper azToRegionMapper;
if (clientConfig.shouldUseDnsForFetchingServiceUrls()) {
azToRegionMapper = new DNSBasedAzToRegionMapper(clientConfig);
} else {
azToRegionMapper = new PropertyBasedAzToRegionMapper(clientConfig);
}
if (null != remoteRegionsToFetch.get()) {
azToRegionMapper.setRegionsToFetch(remoteRegionsToFetch.get().split(","));
}
instanceRegionChecker = new InstanceRegionChecker(azToRegionMapper, clientConfig.getRegion());
} catch (Throwable e) {
throw new RuntimeException("Failed to initialize DiscoveryClient!", e);
}
//获取与注册备份表
if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
fetchRegistryFromBackup();
}
// call and execute the pre registration handler before all background tasks (inc registration) is started
if (this.preRegistrationHandler != null) {
this.preRegistrationHandler.beforeRegistration();
}
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);
}
}
//启动定时任务
// finally, init the schedule tasks (e.g. cluster resolvers, heartbeat, instanceInfo replicator, fetch
initScheduledTasks();
try {
//向注册表中注册一个监听器,当前类实现了 EurekaClient 接口,而 EurekaClient接口中 registerEventListener() 注册监听器
Monitors.registerObject(this);
} catch (Throwable e) {
logger.warn("Cannot register timers", e);
}
// This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()
// to work with DI'd DiscoveryClient
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());
}
ok,初始化过程分析完后,大概的对这个类有了些认知。可以看得出这是一个比较重要的类。在我们包含服务的 注册,缓存,心跳等主要功能。
上面只是大概的分析,这里针对上面的分析再做梳理,总结,以及扩展延伸点。一步一步把核心挖出来。
分析的套路
如果仔细留意我上面是如果去分析类或方法的作用时,基本套路就是:文档注释、类/方法名、内部变量与值,最后通过哪里调用,调用时都做了什么、以及继承体系,形成一个大概的认知。
在分析这些的过程中将一些认为主要的东西作为延伸点,分析完这个流程之后,再去分析延伸点进行细化分析。如果细化分析中,又发现一些主要的流程或者逻辑,再作为延伸点继续分析。一点一点把所有的东西都给连起来,最后渗透整个核心东西
最后肯定少不了整理总结,
总结一些类的作用
DiscoveryClient:
这个就是我们 dubug跟的类,非常核心的一个类。其中包括服务的初始化(服务端/客户端),注册、续约、缓存注册表。。。
之前看 Eureka 时候的理论说明,几乎都是在这里实现的。
**ApplicationInfoManager**:
在初始化注册时所需的类,是针对整个服务的信息与管理。其中设置当前服务的状态,与注册见监听器。
在初始化线程 initScheduledTasks() 任务时候,就会注册一个监听器,监听器当前服务的状态,如果下线了怎么处理,如果没下线刷新什么东西
里面的又有成员变量 InstanceInfo instanceInfo 当前实例的信息, EurekaInstanceConfig config 当前实例的配置信息。
所以是一个大而全的管理类,获得了这个类的实例,就相当于获得了这个服务的信息
**EurekaClientConfig(接口) --> EurekaClientConfigBean(实现类)**:
Eureka 配置类,这个是一个接口,接口的规则目的就是为了获取 Eureka 配置的信息。它有2个实现类 DefaultEurekaClientConfig 与 EurekaClientConfigBean
而实例化时候使用的 EurekaClientConfigBean。里面有大量的配置字段
**Applications**:
主要存放从 Eureka 服务端全部获取的注册表信息,用作本地缓存。而 Application 为对应的每个服务的信息
**BackupRegistry**:
向外提供从本地缓存注册表中查找注册表信息的,也是一个接口,实现类为:NotImplementedRegistryImpl,就2个方法:
fetchRegistry():获取全部注册表信息
fetchRegistry(String[] includeRemoteRegions):根据实例id,获取实例信息
而调用上面2个方法只有在 初始化时候,如果远程获取注册表示失败才会调用 fetchRegistryFromBackup() 从备份中获取注册表
**InstanceInfo**:
也是存放注册表信息的。跟了他的 Application addInstance(InstanceInfo i) 才明白,三者的关系
Applications(存放全部的服务) > Application(存放某个服务的信息) > InstanceInfo(某个服务中可能存在 N 个节点,内部又有一个id标识唯一)。
延伸点
这里主要围绕定时任务看下,都做了什么。而且在定时任务里面能不能再找到延伸点,一下定时任务都在 DiscoveryClient 中
cacheRefreshTask:定时刷新注册表缓存
```java
里面调用的方法其实就在在 DiscoveryClient(debug跟踪的) 中的内部类 CacheRefreshThread 去调用 refreshRegistry() 方法
void refreshRegistry() {
try {
boolean isFetchingRemoteRegionRegistries = isFetchingRemoteRegionRegistries();
boolean remoteRegionsModified = false;
// This makes sure that a dynamic change to remote regions to fetch is honored.
String latestRemoteRegions = clientConfig.fetchRegistryForRemoteRegions();
if (null != latestRemoteRegions) {
String currentRemoteRegions = remoteRegionsToFetch.get();
if (!latestRemoteRegions.equals(currentRemoteRegions)) {
// Both remoteRegionsToFetch and AzToRegionMapper.regionsToFetch need to be in sync
synchronized (instanceRegionChecker.getAzToRegionMapper()) {
if (remoteRegionsToFetch.compareAndSet(currentRemoteRegions, latestRemoteRegions)) {
String[] remoteRegions = latestRemoteRegions.split(",");
remoteRegionsRef.set(remoteRegions);
instanceRegionChecker.getAzToRegionMapper().setRegionsToFetch(remoteRegions);
remoteRegionsModified = true;
} else {
logger.info("Remote regions to fetch modified concurrently," +
" ignoring change from {} to {}", currentRemoteRegions, latestRemoteRegions);
}
}
} else {
// Just refresh mapping to reflect any DNS/Property change
instanceRegionChecker.getAzToRegionMapper().refreshMapping();
}
}
//(上面的不知道,不管了。不是这个功能的主要)。远程获取注册表
//fetchRegistry() 细节看下下面代码片段
boolean success = fetchRegistry(remoteRegionsModified);
if (success) {
//记录目前注册表个数
registrySize = localRegionApps.get().size();
//上次成功的注册表获取时间戳
lastSuccessfulRegistryFetchTimestamp = System.currentTimeMillis();
}
if (logger.isDebugEnabled()) {
StringBuilder allAppsHashCodes = new StringBuilder();
allAppsHashCodes.append("Local region apps hashcode: ");
allAppsHashCodes.append(localRegionApps.get().getAppsHashCode());
allAppsHashCodes.append(", is fetching remote regions? ");
allAppsHashCodes.append(isFetchingRemoteRegionRegistries);
for (Map.Entry<String, Applications> entry : remoteRegionVsApps.entrySet()) {
allAppsHashCodes.append(", Remote region: ");
allAppsHashCodes.append(entry.getKey());
allAppsHashCodes.append(" , apps hashcode: ");
allAppsHashCodes.append(entry.getValue().getAppsHashCode());
}
logger.debug("Completed cache refresh task for discovery. All Apps hash code is {} ",
allAppsHashCodes);
}
} catch (Throwable e) {
logger.error("Cannot fetch registry from server", e);
}
}
//查找注册表
private boolean fetchRegistry(boolean forceFullRegistryFetch) {
Stopwatch tracer = FETCH_REGISTRY_TIMER.start();
try {
//获取本地缓存中的所有注册表
Applications applications = getApplications();
/**
以下if分析:
shouldDisableDelta():查看本客户端是否禁用增量获取注册表,增量可以减少流量的注册。
getRegistryRefreshSingleVipAddress():
指示客户端是否仅针对某个注册表信息感兴趣。分析了下这个方法,可能就是指针对某些注册表更新。就算全量更新时候
也还是会判断这个是否为空,如果不为空也只更新这些
forceFullRegistryFetch:是否强制获取完整的注册表
从上面的方法调用 与 一些条件,分析这个就是判断增量还是全量
*/
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
{
logger.info("Disable delta property : {}", clientConfig.shouldDisableDelta());
logger.info("Single vip registry refresh property : {}", clientConfig.getRegistryRefreshSingleVipAddress());
logger.info("Force full registry fetch : {}", forceFullRegistryFetch);
logger.info("Application is null : {}", (applications == null));
logger.info("Registered Applications size is zero : {}",
(applications.getRegisteredApplications().size() == 0));
logger.info("Application version is -1: {}", (applications.getVersion() == -1));
getAndStoreFullRegistry();
} else {
//增量获取,增量时会有一个 Lock fetchRegistryUpdateLock = new ReentrantLock() 锁,全量不会
getAndUpdateDelta(applications);
}
applications.setAppsHashCode(applications.getReconcileHashCode());
logTotalInstances();
} catch (Throwable e) {
logger.error(PREFIX + "{} - was unable to refresh its cache! status = {}", appPathIdentifier, e.getMessage(), e);
return false;
} finally {
if (tracer != null) {
tracer.stop();
}
}
// 发送一个更新事件给监听器
onCacheRefreshed();
//从服务端端获取实例信息后(服务端会记录本实例的状态信息),将状态信息赋值给存储本实例状态的变量:
//InstanceInfo.InstanceStatus lastRemoteInstanceStatus = InstanceInfo.InstanceStatus.UNKNOWN
updateInstanceRemoteStatus();
// registry was fetched successfully, so return true
return true;
}
heartbeatTask: 服务端发送心跳
DiscoveryClient > HeartbeatThread > renew()
//通过REST调用来使用eureka服务进行续订
boolean renew() {
EurekaHttpResponse<InstanceInfo> httpResponse;
try {
/**
这个方法就比较简单了,可以看出。
先发送心跳,发送了的信息有:服务名、实例id、实例对象信息。
然后再进行注册 register();
*/
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
logger.debug(PREFIX + "{} - Heartbeat status: {}", appPathIdentifier, httpResponse.getStatusCode());
if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
REREGISTER_COUNTER.increment();
logger.info(PREFIX + "{} - Re-registering apps/{}", appPathIdentifier, instanceInfo.getAppName());
long timestamp = instanceInfo.setIsDirtyWithTime();
boolean success = register();
if (success) {
instanceInfo.unsetIsDirty(timestamp);
}
return success;
}
return httpResponse.getStatusCode() == Status.OK.getStatusCode();
} catch (Throwable e) {
logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
return false;
}
}
instanceInfoReplicator:instanceInfo复制器,将本地实例信息,更新与复制到远程
InstanceInfoReplicator instanceInfoReplicator 这个类继承了 Runnable,所以直接看 run() 方法就行
public void run() {
try {
/**
1. 内部会检查当前实例信息(instanceInfo) 中的主机名 与 EurekaInstanceConfig() 中配置的是否一样,
如果不一样就标识存在
instanceInfo.setIsDirty():标识本地缓存与远程注册表是否一致,如果出现脏数据就标识。
2. 会检查 心跳时间 与 没收到心跳包开启自我保护的时间。如果出现与 instanceInfo 不一致,
就重新new LeaseInfo()这个类是instanceInfo中存放那2个时间的实体。然后再 instanceInfo.setLeaseInfo(newLeaseInfo); 更新本地实例信息。
然后再标识脏数据 instanceInfo.setIsDirty():
3. 检查当前实例的状态
*/
discoveryClient.refreshInstanceInfo();
/**
上面的操作是否检查到脏数据,检查到脏数据时候标记true的同时,还会获取当前检测到脏数据的时间。
这里就是获取脏数据的时间。
如果存储脏数据则发送注册当前的 instanceInfo 实例信息。
*/
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 {
Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}