15. SpringCloud之Eureka客户端源码解析

image.png

1、源码入口

SpringCloud是基于Springboot的, 而Springboot最大的特点 就是 自动装配 :通过SPI机制加载META-INF 下的 spring.factories文件, 来 自动的 注册 一些必要的bean,以达到自动装配的效果。

所以我们 读SpringCloud各个组件的源码 也是从这里 直接 去找相关jar包下 META-INFO 下的 spring.factories 文件, 看看自动注册了哪些类的 实例。

eureka的依赖分为 eureka服务端和客户端两部分,我们先从eureka客户端看起。

找到这个jar包

org.springframework.cloud:spring-cloud-netflix-eureka-client:2.0.0.RELEASE2

找到包下的META-INF目录下的spring.factories文件


image.png

可以看到spring.factories文件 以键值对的形式配置了 很多 默认注册的配置类。

key : 为org.springframework.boot.autoconfigure.EnableAutoConfiguration

Value : 为需要自动注册的 类的全限定性名。

Springboot在加载的时候会 把 value集合里配置的 类, 全部 创建bean 注册到spring容器中。

这里关于 eureka客户端的自动配置类 就是这个 org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration。

eureka客户端大部分的功能组件都是在这个类中 注册的。

2、eureka客户端配置信息的加载

先回顾下关于eureka客户端的配置

eureka.client.serviceUrl.defaultZone=http://admin:admin@localhost:9001/eureka/
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true

eureka.instance.health-check-url-path=/actuator/health

大致分为两部分:

  1. 以eureka.client为前缀的, 配置的是一些 eureka客户端的相关信息,比如是否需要注册到eureka服务端,向哪注册, 是否需要从服务端拉取服务列表等等,对应 EurekaClientConfigBean类。
  2. 以eureka.instance为前缀的, 配置的是 一些eureka实例的信息, 比如健康检测的接口路径等,对应EurekaInstanceConfigBean。

2.1、EurekaClientConfigBean类

2.1.1、类简介

这个类负责装载 以eureka.client为前缀的 配置信息,

image.png

下面还有很多 属性, 写配置信息的时候有不懂的可以点到这个类里面看默认值。

类上有 @ConfigurationProperties(EurekaClientConfigBean.PREFIX) 注解, 负责读取 eureka.client为前缀的配置信息。

2.1.2、bean实例化

前面讲了,通过SPI会注册org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration类,点到这里类里面去。

image.png

可以看到通过方法上 有@Bean注解, 注册了EurekaClientConfigBean实例。

当然这个时候, new 出来的EurekaClientConfigBean对象里的 配置属性都是默认的, 这个方法还只是 bean的实例化阶段。

在 bean的 初始化阶段, 由于类上有 @ConfigurationProperties注解, 会通过BeanPostProcessor组件 , 从配置信息中,依赖注入到 对象的配置属性上。

2.2、EurekaInstanceConfigBean类

类简介

这个类用来装载 以eureka.instance为前缀的配置信息

image.png

2.2.1、bean实例化

同样是在 EurekaClientAutoConfiguration 类中,通过@Bean的方式 注册了这个类的实例。

image.png

并且 里面的配置 属性 是 自己从 environment对象 读取出来 赋值的。

2.3、eureka客户端 实例的创建

2.3.1、CloudEurekaClient类

功能简介

CloudEurekaClient这个里 包含了 大部分 eureka客户端的功能,内部持有 :

  1. EurekaClientConfig实例,EurekaInstanceConfig 实例,也就是 eureka客户端的所有配置信息,
  2. 还会创建各种线程池 定时器,结合 配置信息, 来完成 服务注册, 服务发现(拉取服务列表), 续约和保活(发送心跳)等 eureka客户端的核心功能。

2.3.1.1、实例化

同样是在 EurekaClientAutoConfiguration 类中,有一个内部类RefreshableEurekaClientConfiguration。

类上有@Configuration注解,说明会注册到Spring容器中。

image.png

然后, 用方法+@Bean的方式 注册了EurekaClient 实例。这里返回的是 子类CloudEurekaClient 的实例。并且方法入参 会从Spring容器中 传入EurekaClientConfig实例,EurekaInstanceConfig 实例, 并传入到了 构造方法中, 进行 成员变量的赋值。

CloudEurekaClient构造方法

image.png

调用父类DiscoveryClient的构造方法

image.png

赋给父类继承来的成员变量 clientConfig。


image.png

3、服务注册, 服务发现(拉取服务列表), 续约和保活(发送心跳)功能源码

3.1、线程池的定义

在父类DiscoveryClient的构造方法下面,还会创建 线程池来完成 以上核心功能。

前面讲到, 从构造方法的入参中,已经传入了有关客户端的配置信息,EurekaClientConfig实例。在完成一些赋值操作之后, 下面判断 是否 需要注册到服务端, 并且是否需要拉去服务列表,都不需要,直接返回。 eureka服务端 会走到这里。

image.png

eureka客户端 继续往下走,会创建3个线程池。

image.png
  • scheduler 是 通过Executors.newScheduledThreadPool 来创建的,很明显是可以 定时执行的。scheduler 会 定时的向heartbeatExecutor和cacheRefreshExecutor提交对应的任务。
  • heartbeatExecutor 是 用来发送心跳的线程池。
  • cacheRefreshExecutor 是 用来 刷新 服务列表的线程池。

scheduler,heartbeatExecutor和cacheRefreshExecutor这3个线程池的核心线程数时写死的, heartbeatExecutor和cacheRefreshExecutor这两个线程池的非核心线程 可以通过对应的配置信息来配, 默认值为2


image.png

构造方法代码继续往下,走到 initScheduledTasks()。

image.png

initScheduledTasks() 则 开始 向线程池 提交 核心功能的任务了。核心功能的逻辑全在这个方法里。

下面按照 功能一个一个来讲。

3.2、服务发现(拉取服务列表)

initScheduledTasks() 一点进来看到的就是这个

image.png

到这里才看到 scheduler线程池的用处

会提交一个定时执行的 TimedSupervisorTask, TimedSupervisorTask 会持有 cacheRefreshExecutor线程池, 并且在 run方法中,往cacheRefreshExecutor 线程池 提交CacheRefreshThread(实现Runable接口)任务。

简单理解一下,就是 scheduler线程池 会定时执行这样一段逻辑 : 往cacheRefreshExecutor 线程池提交CacheRefreshThread(实现Runable接口)任务。

image.png

频率为多少,从 配置信息里获取,就是这个配置getRegistryFetchIntervalSeconds。

对应这个配置

eureka.client.registry-fetch-interval-seconds=30

TimedSupervisorTask用 线程池的submit方法 提交任务的,并用Feture.get同步阻塞式的获取结果 , 超时时间 也是 上述这个配置。 可以理解为,最多30秒, 下一次 拉取服务列表的动作 都要开始了,这次还没成功的 拉取到服务列表,那就 放弃这次,直接寄希望于下一次。

3.2.1、CacheRefreshThread类

往线程池提交的任务就是这个类的实例, 那么拉取服务列表的 的主要逻辑 肯定是在 CacheRefreshThread这个类里面了。

CacheRefreshThread实现Runnable接口,直接看run方法

image.png
image.png

分为 全量拉取和 更新有变化的部分

image.png

3.2.1.1、全量更新

第一次进来肯定是全量拉取的

请求服务端接口 /apps

先看全量拉取getAndStoreFullRegistry();

这里如果没有配置只从某一台注册中心实例拉取的话,那么就走第一个分支eurekaTransport.queryClient.getApplications

image.png

走到 AbstractJerseyEurekaHttpClient的getApplications方法

image.png

接下来就 是 请求 eureka服务端的 接口

接口地址为

http://admin:admin@localhost:9001/eureka/apps/

最终从服务端接口中 拿到返回值 ,服务列表

服务列表里 包含了每一个服务名称对应的实例列表,每一个实例 的 ip,端口, 状态等信息。

image.png

对应 eureka界面 服务列表

image.png
更新本地服务列表

最终把这个服务列表存储到本地localRegionApps变量里,为后续的远程调用做准备。

image.png
image.png

3.2.1.2、更新有变化的部分

第一次全量更新了之后,后续肯定就没必要在全量更新了,所有后面 只更新有变化的部分。

请求服务端接口 /apps/delta

这不是新冠变种 delta 德尔塔嘛,晦气。哈哈,开个玩笑。

image.png

一开始 肯定也是 请求接口


image.png

只不过 请求接口的地址变了,是 /apps/delta

image.png

发起http请求的部分调的公用的方法,这里就不赘述了。

更新本地服务列表

最终根据接口返回的服务列表, 遍历每一个 服务实例, 里面有一个操作类型 ActionType,根据操作类型 刷新自己本地的服务列表。

  • ActionType = ADD 新增
  • ActionType = MODIFY 更新
  • ActionType = DELETED 删除

还是更新这个 localRegionApps 变量。

image.png
image.png

3.3、续约和保活(发送心跳)

代码回到 initScheduledTasks() 初始化定时器的地方

在初始化好 拉取服务列表的 定时器之后,下面就是 初始化 发送心跳的定时器

image.png

定时提交任务的方式 和 服务发现的如出一辙 : scheduler线程池 定时 执行TimedSupervisorTask任务:向 心跳线程池heartbeatExecutor 提交HeartbeatThread 任务。

所以核心的流程还是 在HeartbeatThread 类中。

直接看run方法

image.png

renew() :

肯定又向 eureka服务端 发请求了

image.png

其实这个判断404的代码分支 很有迷惑性,一开始以为 注册是走这里的,其实不是, 这里面触发的注册 可能的场景是 , 自己通过 SpringCloudBus刷新配置了,比如把服务名称,id都刷新了, 那么 这个时候 发送心跳的接口地址就变了,那么就服务端还没有存有 这个服务名称和id的实例信息,那么 这个接口就会404, 所以 需要重新注册一下。 正常启动 注册是不会走这里的( 不可能定时器 30s之后才第一次注册,那也太慢了)。

把自己的appName和id,还有状态传过去, 让 服务端 更新 自己这个实例的 状态信息。

image.png

这个接口 我们也可以 自己手动调, 如果 我们明确知道某个实例出现故障下线了,然后 那个实例 的定时器 还没有到 发送心跳给服务端的时间点 ,还没有把自己down的状态发给服务端, 那么, 其他 新上线的 服务从服务端 拉取到的服务列表里,这个实例的状态还是UP, 那么调用的时候 还有可能调到他, 就会产生一次失败的调用。 那么 这个时候,就可以自己 调这个接口,把这个实例的状态更新为 DOWN。减少 其他服务调用此服务实例失败的可能。(服务端 把这个实例更新成DOWN之后,其他服务拉取服务列表的定时器还没到时间点, 本地的服务列表还是旧的,这个实例的状态还是UP,那么还会调到它,最终失败。)

3、服务注册

Eureka客户端肯定是 自身已经完全启动后好之后,再向eureka服务端 注册的。

实际上,eureka客户端 的 注册时机 就是与 spring的生命周期 相契合,在 Spring容器加载完成之后 ,再发起注册。

一开始讲到的Spi机制会注册org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration 这个类。

3.1、注册EurekaClientAutoConfiguration

这个类EurekaClientAutoConfiguration 用 @Bean+方法 注册 EurekaRegistration实例,

并传入eurekaClient,instanceConfig 配置信息,applicationInfoManager 应用管理器对象

image.png

3.2、注册 EurekaAutoServiceRegistration

再@Bean+方法 注册 EurekaAutoServiceRegistration 这个类的实例


image.png

EurekaAutoServiceRegistration 这个类 实现了SmartLifecycle 接口,,那么Spring加载完成后会调用到他的SmartLifecycle 接口的start()方法。

3.3、EurekaAutoServiceRegistration.start()

Spring核心方法refresh() ,在bean实例化完成之后会调用finishRefresh()

image.png
image.png

startBeans 方法就会从Spring容器里取出SmartLifecycle 类型的bean,调用它的start方法

image.png

image.png

然后进入 EurekaAutoServiceRegistration.satrt方法,用构造方法赋好值之后的serviceRegistry对象的register 方法注册。

image.png

3.3.1、发布UP状态的事件

注册其实就是发布了一个 UP状态的事件。

image.png
image.png

3.3.2、状态监听器响应事件

这个事件对应的事件监听器 是在 DiscoveryClient 初始化 线程池的initScheduledTasks()里实例化的,是一个匿名对象

image.png

InstanceInfoReplicator 实现了Runable, 这里用schedule线程池 提交了一个任务, 在任务里调InstanceInfoReplicator的run方法

image.png

run方法又调discoveryClient的register()方法


image.png
3.3.2.1、调服务端的服务注册接口

后面就是 调服务端的服务注册接口了

image.png

path是apps/{appName}, 参数是 当前实例的信息

image.png

最终 服务端 返回 状态码204,表示 注册成功!

image.png

你可能感兴趣的:(15. SpringCloud之Eureka客户端源码解析)