Eureka Client 源码解析
- 读取应用自身配置信息
- 服务发现客户端
- 拉取注册表信息
- 服务注册
- 初始化定时任务
- 服务下线
看本篇之前请先看
五分钟学会 Spring Cloud Eureka:服务注册与发现(小白必看,一看就会教程)
Eureka Client 为了简化开发人员的开发工作,将很多与Eureka Server 交互的工作隐藏起来,自主完成
为了跟踪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中的属性读取和配置类。
下面我们对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 中的服务发现方法.
服务发现客户端
为了对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 中对象销毁前执行的清理方法如下所示:
在销毁DiscoveryClient 之前,会进行一系列清理工作,包括注销ApplicationinfoManager中的StatusChangeListener 、取消定时任务、服务下线和关闭Jersey 客户端等。我们主要关注unregister 服务下线方法.
服务下线的接口地址为apps /${APP_NAME} / ${INSTANCE_INFO_ID },传递参数为服务名和服务实例i d, HTTP 方法为delete。
本文同步分享在 博客“码上代码”(CSDN)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。