【SpringCloud】Eureka Client源码分析
上一节Eureka Server 源码分析讲述了 Eureka Server 的原理及部分源码,今天咱们来看看 Eureka Client 端的源码,功能点类似 Eureka Server。
3.7、Eureka Client 源码分析
Eureka Client 通过 Starter 的方式引入依赖, SpringBoot 将会为项目使用以下的自动配置类:
EurekaClientAutoConfiguration:Eureka Client 自动配置类,负责 Eureka Client 中关键Bean的配置和初始化;
RibbonEurekaAutoConfiguration:Ribbon 负载均衡相关配置;
EurekaDiscoveryClientConfiguration:配置自动注册、服务发现和应用的健康检查器。
3.7.1、读取应用自身配置信息
DiscoveryClient 是 Spring Cloud 中用于进行服务发现的顶级接口,也是核心接口,在 Netflix Eureka 或者 Alibaba Nacos 或者 Consul 中都有相应的具体实现类。
public interface DiscoveryClient extends Ordered {
/** * Default order of the discovery client. */int DEFAULT_ORDER = 0;/** * A human-readable description of the implementation, used in HealthIndicator. * @return The description. */ //获取实现类的描述String description();/** * Gets all ServiceInstances associated with a particular serviceId. * @param serviceId The serviceId to query. * @return A List of ServiceInstance. */ //通过服务id获取服务实例的信息List<ServiceInstance> getInstances(String serviceId);/** * @return All known service IDs. */ //获取所有服务的实例idList<String> getServices();/** * Default implementation for getting order of discovery clients. * @return order */@Overridedefault int getOrder() {
return DEFAULT_ORDER;}}
而在 Eureka 方面的实现,主要的实现类即为 EurekaDiscoveryClient。但是仔细看 EurekaDiscoveryClient 代码中会发现它会使用原生的 Eureka 中的代码:
public class EurekaDiscoveryClient implements DiscoveryClient {
//other... //引入原生的EurekaClient接口 private final EurekaClient eurekaClient; @Override public String description() {
return DESCRIPTION; } @Override public List<ServiceInstance> getInstances(String serviceId) {
List<InstanceInfo> infos = this.eurekaClient.getInstancesByVipAddress(serviceId, false); List<ServiceInstance> instances = new ArrayList<>(); for (InstanceInfo info : infos) {
instances.add(new EurekaServiceInstance(info)); } return instances; } @Override public List<String> getServices() {
Applications applications = this.eurekaClient.getApplications(); if (applications == null) {
return Collections.emptyList(); } List<Application> registered = applications.getRegisteredApplications(); List<String> names = new ArrayList<>(); for (Application app : registered) {
if (app.getInstances().isEmpty()) {
continue; } names.add(app.getName().toLowerCase()); } return names; }}
此时的 EurekaClient 接口所在的包为 com.netflix.discovery
,也就是说 Spring Cloud 通过内部组合方式调用了原生 Eureka 中的服务发现方法。而该 EurekaClient 接口的实现类默认是 DiscoveryClient 类,而该类属于原生 Eureka 中的服务发现类,所在的包为com.netflix.discovery
,是不是有点迷糊了。
仔细看代码,就会发现 Spring Cloud 中 DiscoveryClient 接口中的几个方法都是依靠 Eureka 原生接口 EurekaClient 来实现的,而原生 EurekaClient 默认指定的实现类为 DiscoveryClient ,所以归根到底主要看 DiscoveryClient 源码。
3.7.2、服务发现:DiscoveryClient
在讲解 Eureka Server 的时候,InstanceRegistry 也实现了 LookupService 接口, 同样原生的 EurekaClient 也实现了该接口,并在原来的基础上新增了很多检索服务的方法,有兴趣的朋友可以查看:
提供了多种方式获取 InstanceInfo,例如根据区域、地址等方式;
提供了为客户端注册和获取服务健康检查处理器的能力。
除去一般的检索服务的接口,主要关注 EurekaClient中的两个接口方法,分别是:
//DiscoveryClient#registerHealthCheck// 为Eureka Client注册健康检查处理器public void registerHealthCheck(HealthCheckHandler healthCheckHandler) {
if (instanceInfo == null) {
logger.error("Cannot register a healthcheck handler when instance info is null!"); } if (healthCheckHandler != null) {
this.healthCheckHandlerRef.set(healthCheckHandler); // schedule an onDemand update of the instanceInfo when a new healthcheck handler is registered if (instanceInfoReplicator != null) {
instanceInfoReplicator.onDemandUpdate(); } }}//DiscoveryClient#registerEventListener// 监听Client服务实例信息的更新public void registerEventListener(EurekaEventListener eventListener) {
this.eventListeners.add(eventListener);}
Eureka Server 一般通过心跳 (heartbeat)来识别一个实例的状态。Eureka Client 中存在一个定时任务定时通过 HealthCheckHandlerClient 检测当前 Client 的状态 ,如 Client 的状态发生改变, 将会触发新的注册事件 ,更新 Eureka Server 注册表中该服务实例的相关信息。
HealthCheckHandler接口代码如下:
public interface HealthCheckHandler {
InstanceInfo.InstanceStatus getStatus(InstanceInfo.InstanceStatus currentStatus);}
在spring-cloud-netflix-eureka-client
中的实现主要是EurekaHealthCheckHandler, 它主要使用了spring-cloud-actuator
中的 HealthAggregator 和 HealthIndicator,用于监测服务实例的状态。
而 EurekaEventListener注册的事件监听模式属于观察者模式,当服务实例的状态发生改变的时候,就会触发事件,仔细观察 EurekaClient中有个方法:
//DiscoveryClient#fireEventprotected void fireEvent(final EurekaEvent event) {
for (EurekaEventListener listener : eventListeners) {
try {
listener.onEvent(event); } catch (Exception e) {
logger.info("Event {} throw an exception for listener {}", event, listener, e.getMessage()); } }}
该fireEvent
方法即为触发的事件。
3.7.3、DiscoveryClient构造函数
在 DiscoveryClient 构造函数中,Eureka Client 会执行从 Eureka Server 中拉取注册表信息、服务注册、 初始化发送心跳、缓存刷新( 重新拉取注册表信息 )和按需注册定时任务等操作,可以说 DiscoveryClient 的构造函数贯穿 Eureka Client 启动阶段的各项工作。
@InjectDiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
if (args != null) {
this.healthCheckHandlerProvider = args.healthCheckHandlerProvider; this.healthCheckCallbackProvider = args.healthCheckCallbackProvider; this.eventListeners.addAll(args.getEventListeners()); this.preRegistrationHandler = args.preRegistrationHandler; } else {
this.healthCheckCallbackProvider = null; this.healthCheckHandlerProvider = null; this.preRegistrationHandler = null; } this.applicationInfoManager = applicationInfoManager; InstanceInfo myInfo = applicationInfoManager.getInfo(); clientConfig = config; staticClientConfig = clientConfig; transportConfig = config.getTransportConfig(); instanceInfo = myInfo; if (myInfo != null) {
appPathIdentifier = instanceInfo.getAppName() + "/" + instanceInfo.getId(); } else {
logger.warn("Setting instanceInfo to a passed in null value"); } this.backupRegistryProvider = backupRegistryProvider; this.endpointRandomizer = endpointRandomizer; this.urlRandomizer = new EndpointUtils.InstanceInfoBasedUrlRandomizer(instanceInfo); localRegionApps.set(new Applications()); fetchRegistryGeneration = new AtomicLong(0); remoteRegionsToFetch = new AtomicReference<String>(clientConfig.fetchRegistryForRemoteRegions()); remoteRegionsRef = new AtomicReference<>(remoteRegionsToFetch.get() == null ? null : remoteRegionsToFetch.get().split(",")); if (config.shouldFetchRegistry()) {
this.registryStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRY_PREFIX + "lastUpdateSec_", new long[]{
15L, 30L, 60L, 120L, 240L, 480L}); } else {
this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC; } if (config.shouldRegisterWithEureka()) {
this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRATION_PREFIX + "lastHeartbeatSec_", new long[]{
15L, 30L, 60L, 120L, 240L, 480L}); } else {
this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC; } logger.info("Initializing Eureka in region {}", clientConfig.getRegion()); if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
logger.info("Client configured to neither register nor query for data."); scheduler = null; heartbeatExecutor = null; cacheRefreshExecutor = null; eurekaTransport = null; instanceRegionChecker = new InstanceRegionChecker(new PropertyBasedAzToRegionMapper(config), clientConfig.getRegion()); // This is a bit of hack to allow for existing code using DiscoveryManager.getInstance() // to work with DI'd DiscoveryClient DiscoveryManager.getInstance().setDiscoveryClient(this); DiscoveryManager.getInstance().setEurekaClientConfig(config); initTimestampMs = System.currentTimeMillis(); logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}", initTimestampMs, this.getApplications().size()); return; // no need to setup up an network tasks and we are done } try {
// default size of 2 - 1 each for heartbeat and cacheRefresh scheduler = Executors.newScheduledThreadPool(2,