Spring Cloud系列(四):Eureka源码解析之客户端
一、自动装配
1、根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-netflix-eureka-client.jar的spring.factories,查看spring.factories如下:
2、进入EurekaClient的自动装配类EurekaClientAutoConfiguration:
3、@ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)也就是说当容器中有EurekaDiscoveryClientCon figuration.Marker.class时,该配置类才起作用。接下来查看spring.factories,发现还有一个配置类EurekaDiscoveryClientConfigura tion,该配置类就刚刚好往容器中导入了EurekaDiscoveryClientConfiguration.Marker。
二、EurekaClient
1、EurekaClientAutoConfiguration配置类
1.1 导入的核心Bean
① EurekaClientConfigBean:初始化eurekaClient配置;
② EurekaInstanceConfigBean:初始化eureka实例配置;
③ EurekaDiscoveryClient:初始化eureka发现客户端;
④ EurekaServiceRegistry:初始化eureka服务注册;
⑤ EurekaAutoServiceRegistration:初始化eureka自动服务注册;
⑥ EurekaClient:初始化eureka客户端;
1.2 初始化EurekaClient
① 首先看下类的继承图:
② CloudEurekaClient构造方法:
点进去会发现它调用父类的构造方法super(applicationInfoManager, config, args);最终来到如下方法:
@Inject
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
//省略......
this.applicationInfoManager = applicationInfoManager;
//1、获取要注册的服务实例信息
InstanceInfo myInfo = applicationInfoManager.getInfo();
//省略......
instanceInfo = myInfo;
//省略......
//2、定义一些Executor
try {
// default size of 2 - 1 each for heartbeat and cacheRefresh
scheduler = Executors.newScheduledThreadPool(2,
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-%d")
.setDaemon(true)
.build());
//心跳Executor
heartbeatExecutor = new ThreadPoolExecutor(
1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
.setDaemon(true)
.build()
); // use direct handoff
//本地缓存刷新Executor
cacheRefreshExecutor = new ThreadPoolExecutor(
1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
.setDaemon(true)
.build()
); // use direct handoff
/**
* 1、初始化定时拉取服务注册信息和服务续约任务
* 2、定义一个状态变化监听
* 3、初始化定时服务注册任务
*/
initScheduledTasks();
//省略......
}
③ 进入initScheduledTasks()方法如下:
private void initScheduledTasks() {
if (clientConfig.shouldFetchRegistry()) {
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
//每隔30s执行CacheRefreshThread,刷新本地缓存
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
new DiscoveryClient.CacheRefreshThread()
),
registryFetchIntervalSeconds, TimeUnit.SECONDS);
} if (clientConfig.shouldRegisterWithEureka()) {
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
//每隔30s发一次心跳
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new DiscoveryClient.HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS); //定时服务注册
instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
clientConfig.getInstanceInfoReplicationIntervalSeconds(),
2); // burstSize
//状态监听器,当实例状态改变时会调用监听器的notify方法
statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
//省略......
@Override
public void notify(StatusChangeEvent statusChangeEvent) {
//省略......
instanceInfoReplicator.onDemandUpdate();
}
}; if (clientConfig.shouldOnDemandUpdateStatusChange()) {
//往applicationInfoManager里面注册监听器
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
//开启定时服务注册任务
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
} else {
logger.info("Not registering with Eureka server per configuration");
}
}
上面方法核心逻辑:
一:初始化定时拉取服务注册信息和服务续约任务;scheduler.schedule(TimedSupervisorTask),执行TimedSupervisorTask.ru n()方法,定时执行2个任务
Ⅰ、CacheRefreshThread:定时刷新本地注册列表;
Ⅱ、HeartbeatThread:定时向Eureka服务端发送心跳,证明自己还活着;
二:定义一个状态变化监听来监听实例状态的变化;
statusChangeListener = new ApplicationInfoManager.StatusChangeListener(),里面的核心方法notify(StatusChangeE vent statusChangeEvent),该方法里面有个instanceInfoReplicator.onDemandUpdate()方法。然后把statusChangeListener监听器往applicationInfoManager里面注册。当实例状态改变时会调用监听器的notify方法,也就是会调用instanceInfoReplicator.onDema ndUpdate()方法。
三:初始化定时服务注册任务;
调用instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds())方法;进入到InstanceInfo Replicator.start(int initialDelayMs)方法如下:
public void start(int initialDelayMs) {
if (started.compareAndSet(false, true)) {
instanceInfo.setIsDirty(); // for initial register
//延迟40s执行
Future next = scheduler.schedule(this, initialDelayMs, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}
进入到InstanceInfoReplicator.run()方法如下:
public void run() {
try {
discoveryClient.refreshInstanceInfo();
Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
if (dirtyTimestamp != null) {
//服务注册,其实就是调用EurekaServer服务端服务注册接口 httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
discoveryClient.register();
instanceInfo.unsetIsDirty(dirtyTimestamp);
}
} catch (Throwable t) {
logger.warn("There was a problem with the instance info replicator", t);
} finally {
//每隔40s执行一次
Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}
1.3 初始化EurekaAutoServiceRegistration
① EurekaAutoServiceRegistration类的继承图:
实现了SmartLifecycle接口,会在EurekaAutoServiceRegistration初始化完成后,根据isAutoStartup为ture执行start方法。
② 进入EurekaAutoServiceRegistration.start()方法:
public void start() {
//省略......
if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
//服务注册
this.serviceRegistry.register(this.registration);
//发布InstanceRegisteredEvent事件
this.context.publishEvent(new InstanceRegisteredEvent<>(this,
this.registration.getInstanceConfig()));
//设置运行为true
this.running.set(true);
}
}
③ 进入EurekaServiceRegistry.register()方法:
该方法里面reg.getApplicationInfoManager().setInstanceStatus(reg.getInstanceConfig().getInitialStatus());触发状态变化监听器的notify方法,也就是调用到了ApplicationInfoManager.StatusChangeListener.notify方法,notify方法里面执行instanceInfoRep licator.onDemandUpdate()方法,最终调用InstanceInfoReplicator.this.run()方法,进行服务注册。
三、Eureka客户端流程图
自此Eureka客户端源码解析完成,Eureka服务端源码详见:Spring Cloud系列(三):Eureka源码解析之服务端。Eureka应用详见:Spring Cloud系列(二):Eureka应用详解
Spring Cloud系列(四):Eureka源码解析之客户端的更多相关文章
- spring cloud深入学习(四)-----eureka源码解析、ribbon解析、声明式调用feign
基本概念 1.Registe 一一服务注册当eureka Client向Eureka Server注册时,Eureka Client提供自身的元数据,比如IP地址.端口.运行状况指标的Uri.主页地址 ...
- Mybatis 系列6-结合源码解析节点配置:objectFactory、databaseIdProvider、plugins、mappers
[Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...
- Mybatis 系列3-结合源码解析properties节点和environments节点
[Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...
- Mybatis 系列10-结合源码解析mybatis 的执行流程
[Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...
- Mybatis 系列8-结合源码解析select、resultMap的用法
[Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...
- Mybatis 系列7-结合源码解析核心CRUD 配置及用法
[Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...
- Mybatis 系列5-结合源码解析TypeHandler
[Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...
- Mybatis 系列4-结合源码解析节点:typeAliases
[Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...
- 死磕 java同步系列之CyclicBarrier源码解析——有图有真相
问题 (1)CyclicBarrier是什么? (2)CyclicBarrier具有什么特性? (3)CyclicBarrier与CountDownLatch的对比? 简介 CyclicBarrier ...
随机推荐
- Eazfuscator.net 2020 虚拟化保护(Virtulization)机制分析
一.前言与目标 周末接触了一款游戏They are billons即亿万僵尸,想添加一些新的玩法元素比如新的兵种进去, 打开dnspy看了下,发现是Eazfuscator.net的Virtulizat ...
- 【Go语言入门系列】(八)Go语言是不是面向对象语言?
[Go语言入门系列]前面的文章: [Go语言入门系列](五)指针和结构体的使用 [Go语言入门系列](六)再探函数 [Go语言入门系列](七)如何使用Go的方法? 1. Go是面向对象的语言吗? 在[ ...
- php验证当前是设备是电脑端还是手机端
<?php // 验证设备为PC还是WAP $agent = $_SERVER['HTTP_USER_AGENT']; if(strpos($agent,"comFront" ...
- oeasy教您玩转linux010107那啥在哪 whereis
回忆上次内容 上次讲了 ls 的参数 (arguement) 和选项 (option) 的设置. 现在我们要制作这样一个列表:
- 【C#】Random类中构造方法、时间种子与随机数序列的关系
Random类 构造函数 1) Random random = new Random(); // 无参数构造函数使用系统时钟生成其种子值 然而,系统时钟取值范围有限,因此在小规模计算中,可能无法使用不 ...
- 避免nullpointer 空指针
来自知乎: 一般在服务器返回的数据上我们会做数据合法性检测,所以在api文档上需要注明字段的取值范围,然后客户端根据这个去做数据检测,缺段就直接走数据错误的流程,这个很大程度上避免了不少nullpoi ...
- vue中饼状图的使用
图形构建子组件 <template> <div> <div id="myChart" :style="echartStyle"&g ...
- Agumaster页面样式就绪
- 原生 Java 客户端进行消息通信
原生 Java 客户端进行消息通信 Direct 交换器 DirectProducer:direct类型交换器的生产者 NormalConsumer:普通的消费者 MulitBindConsumer: ...
- 【深入理解Linux内核架构】6.6 资源分配
一段摘自<Linux设备驱动程序>的话: 每种外设都通过读写寄存器进行控制.大部分外设都有多个寄存器,不管是内存地址空间还是I/O地址空间,这些寄存器的访问地址都是连续的. 在硬件层,内存 ...