SpringCloud Eureka Client 源码解析

Easter79
• 阅读 599

Eureka Client 源码解析

  • 读取应用自身配置信息
  • 服务发现客户端
  • 拉取注册表信息
  • 服务注册
  • 初始化定时任务
  • 服务下线

看本篇之前请先看
五分钟学会 Spring Cloud Eureka:服务注册与发现(小白必看,一看就会教程)

Eureka Client 为了简化开发人员的开发工作,将很多与Eureka Server 交互的工作隐藏起来,自主完成

SpringCloud Eureka Client 源码解析
为了跟踪Eureka 的运行机制,读者可以通过打开Spring Boot 的Debug 模式来查看更多的输出日志, 如下所示:

logging:
        level:
            org.springframework:DEBUG

Eukeka Client 通过Starter 的方式引人依赖, Spring Boot 将会为项目使用以下的自动配置类:

  • EurekaClientAutoConfiguration : Eureke Client 自动配置类,负责Eureka Client 中关键Beans 的配置和初始化ApplicationlnfoManager 和EurekaC!ientConfig 等。
  • RibbonEurekaAutoConfiguration: Ribbon 负载均衡相关配置。
  • EurekaDiscoveryClientConfiguration :配置自动注册和应用的健康检查器。

读取应用自身配置信息

通过EurekaDiscoveryClientConfiguration 配置类, Spring Boot 帮助Eureka Client 完成很多必要Bean 的属性读取和配置,表4 -1 列出了EurekaDiscoveryClientConfiguration中的属性读取和配置类。

SpringCloud Eureka Client 源码解析
SpringCloud Eureka Client 源码解析
下面我们对Spring Cloud 中的服务发现客户端DiscoveryClient 进行进一步的介绍,它是客户端进行服务发现的核心接口。

DiscoveryClient 是Spring Cloud 中用来进行服务发现的顶级接口,在Netflix Eureka 或者Consul 中都有相应的具体实现类, 在4.1 节基础应用中有所介绍,该接口提供的方法如下:

//DiscoveryClient. java
public interface DiscoveryClient {
   
   
   
//获取实现类的描述
String description() ;
//通过服务工d获取服务实例的信息
List<Service 工nstance> getInstaces(String servceid) ;
//获取所有的服务实例Id
Li st<String> getServices() ;
}

EurekaDiscoveryC!ient 继承了DiscoveryClient 接口,但是通过查看EurekaDiscoveryClient中代码, 会发现它是通过组合EurekaClient 类实现接口的功能,如下为getlnstance 方法的实现:

//EurekaDiscoveryClient Java
@Override
public List<ServiceI口stance> getinstances (String serviceid) {
   
   
   
List<Instanceinfo> infos = this. eurekaClient . getinstancesBypAddress(servi ceId, false);
List<Serviceinstance> instances =new ArrayList<>() ;
for ( Instanceinfo info infos) {
   
   
   
instances .add(new EurekaServiceinstance(info)) ;
}
return instances;
}

EurekaClient 来自于com.netflix.discovery包中,其默认实现为com.netflix.discovery.DiscoveryClient ,属于eureka- client 的源代码,它提供了Eureka Client 注册到Server 上、续租、下线以及获取Server 中注册表信息等诸多关键功能。Spring Cloud 通过组合方式调用了Eureka 中的服务发现方法.
SpringCloud Eureka Client 源码解析

服务发现客户端

为了对Eureka Client 的执行原理进行讲解, 首先需要对服务发现客户端com.netflix.discover.DiscoveryClient 职能以及相关类进行讲解,它负责了与Eureka Server 交互的关键逻辑。

DiscoveryClient 职责
DiscoveryClient 是Eureka C lient 的核心类,包括与Eureka Server 交互的关键逻辑,具备了以下职能:

  • 注册服务实例到Eureka Server 中;
  • 发送心跳更新与Eureka Server 的租约;
  • 在服务关闭时从Eureka Server 中取消租约,服务下线;
  • 查询在Eureka Server 中注册的服务实例列表。

DiscoveryClient 类结构
DiscoveryClient 继承了LookupService接口,LookupService作用是发现活跃的服务实例, 主要方法如下:

//LookupServer
public interface LookupService<T> {
   
   
    •
//根据服务实例注册的appNarne来获取封装有相同appNarne 的服务实例信息容器
Application getApplication(String appName);
//返回当前注册表中所有的服务实例信息
Applications getApplications();
//根据服务实例的id获取服务实例信息
List<Instanceinfo> getinstancesById (String id) ;
}

Application 持有服务实例信息列表,它可以理解成同一个服务的集群信息,这些服务实例都挂在同一个服务名appName 下。InstanceInfo 代表一个服务实例信息。Application部分代码如下:

//Application.java
public class Application {
   
   
   
private static Random shuffleRandom =new Random() ;
//服务名
private String name ;
@XStreamOmitField
private volatile boolean isDirty = false ;
@XStrearnimplicit
private final Set<Instanceinfo> instances ;
private final AtomicReference<List <Instanceinfo> shuffledinstances ;
private final Map<String , Instanceinfo >InstancesMap;
}

为了保证原子性操作, Application 中对Instancelnfo 的操作都是同步操作。Applic ~tions 是注册表中所有服务实例信息的集合, 里面的操作大多也是同步操作。EurekaC!ient 继承了LookupService 接口,为DiscoveryClient 提供了一个上层接口,目的是方便从Eureka l. x 到Eureka 2.x ( 已停止开发) 的升级过渡。EurekaC!ient 接口属于比
较稳定的接口,即使在下一阶段也会被保留。

EurekaCient 在LookupService 的基础上扩充了更多的接口,提供了更丰富的获取服务实例的方式, 主要有:

  • 提供了多种方式获取Instancelnfo , 例如根据区域、Eureka Server 地址等获取。
  • 提供了本地客户端(所处的区域、可用区等) 的数据,这部分与WS 密切相关。
  • 提供了为客户端注册和获取健康检查处理器的能力。

除去查询相关的接口,我们主要关注EurekaClient 中以下两个接口,代码如下所示:

 EurekaClient.java
//为Eureka Client 注册健康检查处理器
public void registerHealthCheck(HealthCheckHandler healthCheckHandler) ;
//为EurekaClient 注册一个EurekaEventListener ( 事件监听器)
//监听Client服务实例信息的更新
publiC  IdregisterEventListener(EurekaEventListe口er eventListener) ;

Eureka Server 一般通过心跳( heartbeats )来识别一个实例的状态。Eureka Client 中存在一个定时任务定时通HealthCheckHandler 检测当前C lient 的状态,如果Client 的状态发生改变, 将会触发新的注册事件,更新E ureka Server 的注册表中该服务实例的相关信息。HealthCheckHandler 的代码如下所示:

// HealthCheckHandler . java
public interface Heal thCheckHandler {
   
   
   
Ins t anceinfo . InstanceStatus getStatus (Instanceinfo . InstanceStatus currentStatus );

HealthCheckHandler 接口的代码如上所示,其在spring-cloud-netflix - eurekaClient 中的实现类为EurekaHealthCheckHandler , 主要组合了spring - boot-actuator 中的HealthAggregator和Healthlndicator ,以实现对Spring Boot 应用的状态检测。

Eureka 中的事件模式属于观察者模式,事件监听器将监昕Client 的服务实例信息变化,触发对应的处理事件.
DiscoveryClient 构造函数
在Discov巳ryC!ient 构造函数中, Eureka Client 会执行从Eureka Server 中拉取注册表信息、服务注册、初始化发送心跳、缓存刷新( 重新拉取注册表信息、)和按需注册定时任务等操作,可以说DiscoveryC!ient 的构造函数贯穿了Eureka Client 启动阶段的各项工作。DiscoveryC!ient 的构造函数传人的参数如下所示:
ApplicationlnfoManager 和EurekaClientConfig 在前面内容中已经做了介绍,一个是应用信息管理器,另一个是封装了Client 与Server 交互配置信息的类。

AbstractDiscoveryClientOptionalArgs 是用于注入一些可选参数,以及一些jerseyl 和jersey2 通用的过滤器。而BackupRegistry 充当了备份注册中心的职责,当Eureka Client 无法从任何一个Eureka Server 中获取注册表信息时, BackupRegistry 将被调用以获取注册表信息。默认的实现是Notlmp lement巳dRegistrylmpl ,即没有实现。

在构造方法中,忽略掉构造方法中大部分的赋值操作,我们逐步了解了配置类中的属性会对DiscoveryClient 的行为造成什么影响。DiscoveryClient 构造函数中的部分代码如下所示:

config#shouldFetchRegistry (对应配置为eureka . client.fetch -register )为true 表示EurekaClient 将从Eureka Server 中拉取注册表信息。config#shouldRegisterWithEureka (对应配置为eureka.client.register- with-eureka )为true 表示Eureka Client 将注册到Eureka Server 中。如果上述的两个配置均为false , 那么Discovery 的初始化将直接结束,表示该客户端既不进行服务注册也不进行服务发现。

接着定义一个基于线程池的定时器线程池Sc heduled ExecutorService ,线程池大小为2,一个线程用于发送心跳, 另一个线程用于缓存刷新,同时定义了发送心跳和缓存刷新线程池,代码如下所示:

//DiscoveryCl 工ent . java
scheduler= Executors . newScheduledThreadPool(2 , new ThreadFactoryBu 工lder().setNameFormat ( ” DiscoveryClient -屯d ” ) . setDaemo true) .build()) ;
heartbeatExecutor =new ThreadPoolExecutor( ... ) ;
cacheRefreshExecutor =new ThreadPoolExecutor( . . . ) ;
}

之后,初始化Eureka C li ent 与Eureka Server 进行HTTP 交五的Jersey 客户端,将AbstractDiscoveryClientOptiona!Args 中的属性用来构建Eureka Transport ,如下所示:

// DiscoveryClient .]ava
eurekaTransport =new EurekaTransport() ;
scheduleServerEndpointTask(eurekaTransport, args) ;

Eureka Transport 是DiscoveryC li ent 中的一个内部类,其内封装了DiscoveryC l ient 与Eureka Server 进行HTTP 调用的Jersey 客户端。
再接着从Eureka Server 中拉取注册表信息,代码如下所示:

// DiscoveryClient. java
if (clientConfig.shouldFetchReg 工stry () && ! fetchRegistry (false) ) {
   
   
   
fetchRegistryFromBackup() ;
}

如果EurekaClientConfig # shouldFetchRegistry 为true 时, fetchRegistry 方法将会被调用。在Eureka Client 向Eureka Server 注册前,需要先从Eureka Server 拉取注册表中的信息,这是服务发现的前提。通过将Eureka Server 中的注册表信息缓存到本地,就可以就近获取其他服务的相关信息, 减少与Eureka Server 的网络通信。

在服务注册之前会进行注册预处理, Eureka 没有对此提供默认实现。构造函数的最后将初始化并启动发送心跳、缓存刷新和按需注册等定时任务。

最后总结一下,在DiscoveryCiient 的构造函数中,主要依次做了以下的事情:

  • 1 )相关配置的赋值,类似ApplicationlnfoManager 、EurekaClientConfig 等。
  • 2 )备份注册中心的初始化,默认没有实现。
  • 3 )拉取Eureka Server 注册表中的信息。
  • 4 ) 注册前的预处理。
  • 5 )向Eureka Server 注册自身。
  • 6 )初始化心跳定时任务、缓存刷新和按需注册等定时任务。

拉取注册表信息

在Eureka 客户端,除了第一次拉取注册表信息,之后的信息拉取都会尝试只进行增量拉取(第一次拉取注册表信息为全量拉取) , 下面将分别介绍拉取注册表信息的两种实现,全量拉取注册表信息Disc overyClientgetAndStoreFullRegistry 和增量式拉取注册表信息DiscoveryC!ient#getAndUpdateDelta 。

全量拉取注册表信息
一般只有在第一次拉取的时候,才会进行注册表信息的全量拉取,主要在DiscoveryCIientgetAndStoreFulRegistry 方法中进行.

全量拉取将从Eureka Server 中拉取注册表中所有的服务实例信息(封装在Applications中),并经过处理后替换掉本地注册表缓存Applications 。通过跟踪调用链,在AbstractJerseyEurekaHttpC!ient#getApplicationslntemal 方法中发
现了相关的请求时, 接口地址为/ eureka /apps .

增量式拉取注册表信息
增量式的拉取方式, 一般发生在第一次拉取注册表信息之后,拉取的信息定义为从某一段时间之后发生的所有变更信息,通常来讲是3 分钟之内注册表的信息变化。在获取到更新的delta 后,会根据delta 中的增量更新对本地的数据进行更新。与getAndStoreFullRegistry 方法一样,也通过fetchRegistryGen 巳ration 对更新的版本进行控
制。增量式拉取是为了维护Eureka Client 本地的注册表信息与Eureka Server 注册表信息的一致性,防止数据过久而失效,采用增量式拉取的方式减少了拉取注册表信息的通信量。Client 中有一个注册表缓存刷新定时器专门负责维护两者之间信息的同步性。但是当增
量式拉取出现意外时,定时器将执行全量拉取以更新本地缓存的注册表信息。

服务注册

在拉取完Eureka Server 中的注册表信息并将其缓存在本地后, Eureka Client 将向Eureka Server 注册自身服务实例元数据,主要逻辑位于Discovery #register 方法中.

Eureka Client 会将自身服务实例元数据(封装在Instancelnfo 中)发送到Eureka Server中请求服务注册,当Eureka Server 返回204 状态码时,说明服务注册成功。

注册接口地址为apps /${APP_NAME },传递参数为Instancelnfo ,如果服务器返回204状态,则表明注册成功。

初始化定时任务
服务注册应该是一个持续的过程, Eureka Client 通过定时发送心跳的方式与Eureka Server 进行通信,维持自己在Server 注册表上的租约。同时Eureka Server 注册表中的服务实例信息是动态变化的,为了保持Eureka Client 与Eureka Server 的注册表信息的一致性, Eu reka Client 需要定时向Eureka Server 拉取注册表信息并更新本地缓存。为了监控Eureka Client 应用信息和状态的变化, Eureka Client 设置了一个按需注册定时器,定时检查应用信息或者状态的变化, 并在发生变化时向Eureka Server 重新注册,避免注册表中的
本服务实例信息不可用.

服务下线

一般情况下,应用服务在关闭的时候, Eureka Client 会主动向Eureka Server 注销自身在注册表中的信息。DiscoveryC!ient 中对象销毁前执行的清理方法如下所示:
SpringCloud Eureka Client 源码解析
在销毁DiscoveryClient 之前,会进行一系列清理工作,包括注销ApplicationinfoManager中的StatusChangeListener 、取消定时任务、服务下线和关闭Jersey 客户端等。我们主要关注unregister 服务下线方法.
服务下线的接口地址为apps /${APP_NAME} / ${INSTANCE_INFO_ID },传递参数为服务名和服务实例i d, HTTP 方法为delete。

本文同步分享在 博客“码上代码”(CSDN)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
AWS国庆双重礼,仅限7天
自2021年10月1日00:00起至2021年10月7日24:00,新注册并激活(需全部完成账号注册的五个步骤,否则账号状态并未激活)AWS海外区域账户,填写页面下方表单,即可申领价值$200美元的AWS海外区域账户服务抵扣券直充到您的账户,用以抵扣服务消费,助您轻松体验多个云迁移应用场景。同时,您还可获赠AWS精美祥云纪念T恤一件。,仅限7天$20
Easter79 Easter79
3年前
SpringCloud Hystrix源码解析(一)
SpringCloudHystrix源码解析看本篇之前请看五分钟学会SpringCloudHystrix:服务容错保护(小白必看,一看就会系列教程)(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fblog.csdn.net%2Fweixin_443022
Stella981 Stella981
3年前
Discuz X3.2源码解析 discuz_application类(转自百度)
1.discuz\_application在/source/class/discuz/discuz\_application.php中。!DiscuzX3.2源码解析discuz_application类(https://oscimg.oschina.net/oscnet/99b35d79caf70b7c74ad0838d6
Easter79 Easter79
3年前
SpringCloud常用组件
springcloud中有五大核心组件Eureka、Ribbon、Feign、Hystrix、Zuul,简单记录如下。Eureka是微服务架构中的注册中心,专门负责服务的注册与发现。EurekaClient组件专门负责将服务的信息注册到EurekaServer中,而EurekaServer是一个注册中心,里面有一个注册表,保存了各服务所在
Stella981 Stella981
3年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
AWS国庆双重礼,仅限7天
自2021年10月1日00:00起至2021年10月7日24:00,新注册并激活(需全部完成账号注册的五个步骤,否则账号状态并未激活)AWS海外区域账户,填写页面下方表单,即可申领价值$200美元的AWS海外区域账户服务抵扣券直充到您的账户,用以抵扣服务消费,助您轻松体验多个云迁移应用场景。同时,您还可获赠。国庆双重礼,仅限7天$200美元AWS服务抵
Wesley13 Wesley13
3年前
35岁是技术人的天花板吗?
35岁是技术人的天花板吗?我非常不认同“35岁现象”,人类没有那么脆弱,人类的智力不会说是35岁之后就停止发展,更不是说35岁之后就没有机会了。马云35岁还在教书,任正非35岁还在工厂上班。为什么技术人员到35岁就应该退役了呢?所以35岁根本就不是一个问题,我今年已经37岁了,我发现我才刚刚找到自己的节奏,刚刚上路。
Java服务总在半夜挂,背后的真相竟然是... | 京东云技术团队
最近有用户反馈测试环境Java服务总在凌晨00:00左右挂掉,用户反馈Java服务没有定时任务,也没有流量突增的情况,Jvm配置也合理,莫名其妙就挂了
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k