1、DiscoveryClient接口和类

  在SpringCloud框架中,有一个DiscoveryClient接口和一个同名的DiscoveryClient类,其中:DiscoveryClient类是Netflix开源框架提供的,主要用于与Eureka服务端(即注册中心)进行交互;DiscoveryClient接口是SpringCloud框架提供的,主要为了扩展Netflix提供的Eureka客户端而提供的,该接口的实现类通过组合的方式引入了Netflix提供的DiscoveryClient类,然后进行了进一步封装,让开发者更加容易使用SpringBoot进行基于Eureka的开发。

1.1、SpringCloud提供的DiscoveryClient接口


  在SpringCloud提供的DiscoveryClient接口中有四个实现类,分别是:

  1. NoopDiscoveryClient,已经被标记过期
  2. EurekaDiscoveryClient,主要用来当Eureka作为注册中心进行管理服务实例时使用
  3. CompositeDiscoveryClient,是一个组合器,主要用来组合多种DiscoveryClient实现,具体还是交给其他DiscoveryClient实现类进行处理
  4. SimpleDiscoveryClient,主要用来当使用属性文件管理服务实例的场景
1.2、Netflix提供的DiscoveryClient类


  在Netflix提供的DiscoveryClient类的层级结构中,其中,LookupService接口、EurekaClient接口和DiscoveryClient类是Netflix提供的,而最后一个CloudEurekaClient类是SpringCloud基于DiscoveryClient类的扩展,方便与SpringBoot更好的集成。

2、SpringCloud中的DiscoveryClient接口及其实现

  我们这里主要学习DiscoveryClient接口和其对应Eureka服务的实现类EurekaDiscoveryClient。

2.1、DiscoveryClient接口

  DiscoveryClient接口是SpringCloud提供的用来进行服务发现的通用接口,一般可以用于Eureka或consul等,主要提供了读取实例的方法,如下所示:

public interface DiscoveryClient extends Ordered {int DEFAULT_ORDER = 0;//实现类的功能描述,一般用在HealthIndicator的打印日志中String description();//根据服务Id获取对应的服务实例集合List<ServiceInstance> getInstances(String serviceId);//获取所有的服务IdList<String> getServices();@Overridedefault int getOrder() {return DEFAULT_ORDER;}}
2.2、EurekaDiscoveryClient实现类

  EurekaDiscoveryClient实现类就是基于Eureka实现了DiscoveryClient接口。在该实现类中,通过构造函数注入了EurekaClient和EurekaClientConfig属性,这两个属性就是专门用来进行与Eureka服务进行交互的组件,是由Netflix提供的。

2.2.1、getInstances()方法

  该方法中,首先通过调用eurekaClient的getInstancesByVipAddress()方法获取对应的实例集合,然后再遍历集合构造成EurekaServiceInstance集合。

@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;
}
2.2.2、getServices()方法

  该方法主要用来获取Eureka服务端所有的服务实例的,还是通过eurekaClient实例与Eureka服务进行交互,具体实现如下:

@Override
public List<String> getServices() {//获取Eureka服务端服务集合,一般客户端请求会返回该对象Applications applications = this.eurekaClient.getApplications();if (applications == null) {return Collections.emptyList();}//从Applications中获取服务集合Application,每一个Application表示一个服务集合,其中会包含多个InstanceInfo实例List<Application> registered = applications.getRegisteredApplications();List<String> names = new ArrayList<>();//遍历,构建成一个String集合并返回for (Application app : registered) {if (app.getInstances().isEmpty()) {continue;}names.add(app.getName().toLowerCase());}return names;
}

3、ServiceInstance接口及其实现类

  DiscoveryClient接口的getInstances()方法会返回一个ServiceInstance集合,而ServiceInstance是SpringCloud定义的一个在服务发现中表示服务实例的接口,DefaultServiceInstance是该接口的一个默认实现;而EurekaServiceInstance就是表示基于Eureka进行服务发现的服务实例的实现。

3.1、ServiceInstance 接口
public interface ServiceInstance {//实例Iddefault String getInstanceId() {return null;}//服务IdString getServiceId();//主机名或地址String getHost();//端口号int getPort();//是否使用安全协议HTTPSboolean isSecure();//服务的URLURI getUri();//服务实例相关的元数据Map<String, String> getMetadata();//使用方案,一般表示协议,比如http或httpsdefault String getScheme() {return null;}}
3.2、DefaultServiceInstance类

  DefaultServiceInstance类,有点类似在业务系统开发中定义的实体Bean,除了定义了接口中对应方法的属性之外,然后就是提供了构造函数和对应实现的getter方法,这里不再贴出代码了,只看一个getUri()方法,稍微做了一些处理,如下:

public static URI getUri(ServiceInstance instance) {//根据是否开启安全协议,选择https或httpString scheme = (instance.isSecure()) ? "https" : "http";//然后,构建uriString uri = String.format("%s://%s:%s", scheme, instance.getHost(),instance.getPort());return URI.create(uri);
}
3.3、EurekaServiceInstance类

  EurekaServiceInstance类就是表示基于Eureka注册中心进行服务发现的服务实例,其中持有一个InstanceInfo对象表示Eureka注册中心中所使用的的服务实例,然后再扩展在SpringCloud需要的一些属性。

public class EurekaServiceInstance implements ServiceInstance {//InstanceInfo 是Eureka注册中心中使用的服务实例对象private InstanceInfo instance;//通过构造函数注入InstanceInfo 对象public EurekaServiceInstance(InstanceInfo instance) {Assert.notNull(instance, "Service instance required");this.instance = instance;}public InstanceInfo getInstanceInfo() {return instance;}//使用InstanceInfo 对象的id@Overridepublic String getInstanceId() {return this.instance.getId();}//使用InstanceInfo 对象的appName@Overridepublic String getServiceId() {return this.instance.getAppName();}//使用InstanceInfo 对象的hostName@Overridepublic String getHost() {return this.instance.getHostName();}//使用InstanceInfo 对象的port,如果安全协议使用securePort端口@Overridepublic int getPort() {if (isSecure()) {return this.instance.getSecurePort();}return this.instance.getPort();}//判断是否开启了Https协议,还是基于InstanceInfo对象判断@Overridepublic boolean isSecure() {// assume if secure is enabled, that is the defaultreturn this.instance.isPortEnabled(SECURE);}//获取对应的uri,格式:“协议://host:port”,其中协议可选http或https@Overridepublic URI getUri() {return DefaultServiceInstance.getUri(this);}//使用InstanceInfo 对象的元数据@Overridepublic Map<String, String> getMetadata() {return this.instance.getMetadata();}//使用URL的模式,http或https@Overridepublic String getScheme() {return getUri().getScheme();}//……省略
}

4、Netflix提供的DiscoveryClient类的层级结构

  根据前面类的层级结构图,我们可以知道DiscoveryClient类是Netflix框架提供的服务发现的最终的组件了(Spring Cloud又提供了一个DiscoveryClient类的子类CloudEurekaClient),而DiscoveryClient类实现了EurekaClient接口,EurekaClient接口又继承了LookupService接口。

4.1、LookupService接口

  LookupService<T>接口主要用来进行服务发现,即查找活动的服务实例。

public interface LookupService<T> {//根据appName查找对应的Application对象,该对象维护了一个服务实例列表,即Application对象维护了一个指定应用的服务实例列表的容器。Application getApplication(String appName);//包装了Eureka服务返回的全部注册信息,其中维护了一个Application对象的集合Applications getApplications();//根据实例Id,查询对应的服务实例列表List<InstanceInfo> getInstancesById(String id);//获取下一个用于处理请求的服务实例(只返回UP状态的服务实例,可以通过重写EurekaClientConfig#shouldFilterOnlyUpInstances()方法进行修改)InstanceInfo getNextServerFromEureka(String virtualHostname, boolean secure);
}
4.2、EurekaClient接口

  EurekaClient接口扩展了服务发现接口LookupService,该接口提供了用于Eureka1.x到Eureka2.x的迁移的方法。默认使用了DiscoveryClient实现类,然后增强了如下能力:

  1. 提供了获取InstanceInfo实例的方法
  2. 提供获取Eureka 客户端的信息,比如regions等的方法
  3. 为客户端提供注册和访问healthcheck处理器的能力
@ImplementedBy(DiscoveryClient.class)
public interface EurekaClient extends LookupService {//获取指定region下的Applications对象public Applications getApplicationsForARegion(@Nullable String region);//根据serviceUrl获取Applications 对象public Applications getApplications(String serviceUrl);//获取匹配VIP地址的InstanceInfo集合public List<InstanceInfo> getInstancesByVipAddress(String vipAddress, boolean secure);//获取匹配VIP地址、region的InstanceInfo集合public List<InstanceInfo> getInstancesByVipAddress(String vipAddress, boolean secure, @Nullable String region);//获取匹配VIP地址、appName的InstanceInfo集合public List<InstanceInfo> getInstancesByVipAddressAndAppName(String vipAddress, String appName, boolean secure);//获取能够被当前客户端访问的所有region,包括local和remotepublic Set<String> getAllKnownRegions();//Eureka服务端当前实例的状态public InstanceInfo.InstanceStatus getInstanceRemoteStatus();//该方法已经迁移到了EndpointUtils类中,获取当前客户端所在zone的所有可以通信的Eureka服务端的URL集合@Deprecatedpublic List<String> getDiscoveryServiceUrls(String zone);//该方法已经迁移到了EndpointUtils类中,获取当前客户端所在zone的所有可以通信的Eureka服务端的URL集合(在配置文件中配置的URL)@Deprecatedpublic List<String> getServiceUrlsFromConfig(String instanceZone, boolean preferSameZone);//该方法已经迁移到了EndpointUtils类中,获取当前客户端所在zone的所有可以通信的Eureka服务端的URL集合(从DNS中)@Deprecatedpublic List<String> getServiceUrlsFromDNS(String instanceZone, boolean preferSameZone);//标记过期,已迁移到com.netflix.appinfo.HealthCheckHandler类,为客户端提供注册HealthCheckCallback。@Deprecatedpublic void registerHealthCheckCallback(HealthCheckCallback callback);//注册HealthCheckHandlerpublic void registerHealthCheck(HealthCheckHandler healthCheckHandler);//注册EurekaEventListener,用于监控客户端内部的状态变化public void registerEventListener(EurekaEventListener eventListener);//解除注册的EurekaEventListenerpublic boolean unregisterEventListener(EurekaEventListener eventListener);//获取注册的HealthCheckHandler 对象public HealthCheckHandler getHealthCheckHandler();//关闭客户端public void shutdown();//获取客户端配置对象EurekaClientConfigpublic EurekaClientConfig getEurekaClientConfig();//获取ApplicationInfoManager对象public ApplicationInfoManager getApplicationInfoManager();
}
4.3、DiscoveryClient类

  DiscoveryClient类主要就是客户端用来与Eureka服务端进行交互的类,主要实现以下功能:

  1. 初始化,开启定时任务(续约、心跳等)
  2. 服务注册
  3. 服务续约
  4. 取消租约
  5. 查询注册的服务或服务实例
  6. 其他
4.3.1、DiscoveryClient构造函数

  DiscoveryClient类有很多个构造函数,而且还有一些已经被打上弃用标签,如下所示,我们这里选择最后一个进行分析和学习,其他构造函数最终都是调用了这个构造函数。

  DiscoveryClient构造函数的部分代码:

@Inject
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {//省略 变量赋值……logger.info("Initializing Eureka in region {}", clientConfig.getRegion());//当客户端不需要注册自身到服务端和获取Eureka服务端注册信息时,处理其中的对象,一般设置为nullif (!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 DiscoveryClientDiscoveryManager.getInstance().setDiscoveryClient(this);DiscoveryManager.getInstance().setEurekaClientConfig(config);initTimestampMs = System.currentTimeMillis();initRegistrySize = this.getApplications().size();registrySize = initRegistrySize;logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",initTimestampMs, initRegistrySize);return;  // no need to setup up an network tasks and we are done}try {// 创建一个定长的线程池,而且支持周期性的任务执行scheduler = Executors.newScheduledThreadPool(2,new ThreadFactoryBuilder().setNameFormat("DiscoveryClient-%d").setDaemon(true).build());//创建用于心跳发送的线程池,创建核心线程数为1,最大线程池为2,空闲时间0,单位秒,等待队列,创建工厂heartbeatExecutor = new ThreadPoolExecutor(1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(),new ThreadFactoryBuilder().setNameFormat("DiscoveryClient-HeartbeatExecutor-%d").setDaemon(true).build());  // use direct handoff//创建用于缓存刷新的线程池cacheRefreshExecutor = new ThreadPoolExecutor(1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(),new ThreadFactoryBuilder().setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d").setDaemon(true).build());  // use direct handoff//构建EurekaTransport对象,它是Eureka 服务端与客户端进行http交互的jersey组件eurekaTransport = new EurekaTransport();//初始化 EurekaTransport对象scheduleServerEndpointTask(eurekaTransport, args);AzToRegionMapper azToRegionMapper;//主要用于 AWS,处理Region相关内容if (clientConfig.shouldUseDnsForFetchingServiceUrls()) {azToRegionMapper = new DNSBasedAzToRegionMapper(clientConfig);} else {azToRegionMapper = new PropertyBasedAzToRegionMapper(clientConfig);}if (null != remoteRegionsToFetch.get()) {azToRegionMapper.setRegionsToFetch(remoteRegionsToFetch.get().split(","));}//应用实例信息区域( region )校验instanceRegionChecker = new InstanceRegionChecker(azToRegionMapper, clientConfig.getRegion());} catch (Throwable e) {throw new RuntimeException("Failed to initialize DiscoveryClient!", e);}//首先,判断是否允许拉去Eureka服务端的注册数据if (clientConfig.shouldFetchRegistry()) {try {//获取Eureka服务端的注册数据boolean primaryFetchRegistryResult = fetchRegistry(false);if (!primaryFetchRegistryResult) {logger.info("Initial registry fetch from primary servers failed");}boolean backupFetchRegistryResult = true;//从备份中获取Eureka服务端的注册数据if (!primaryFetchRegistryResult && !fetchRegistryFromBackup()) {backupFetchRegistryResult = false;logger.info("Initial registry fetch from backup servers failed");}//当没有获取到注册数据,且要求启动时进行初始化,这个时候就会跑出异常if (!primaryFetchRegistryResult && !backupFetchRegistryResult && clientConfig.shouldEnforceFetchRegistryAtInit()) {throw new IllegalStateException("Fetch registry error at startup. Initial fetch failed.");}} catch (Throwable th) {logger.error("Fetch registry error at startup: {}", th.getMessage());throw new IllegalStateException(th);}}// 回调,用于扩展if (this.preRegistrationHandler != null) {this.preRegistrationHandler.beforeRegistration();}//判断是否在初始化时,进行注册if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {try {if (!register() ) {throw new IllegalStateException("Registration error at startup. Invalid server response.");}} catch (Throwable th) {logger.error("Registration error at startup: {}", th.getMessage());throw new IllegalStateException(th);}}// 初始化定时任务,用于心跳、节点数据同步、获取Eureka服务端数据等。initScheduledTasks();//省略 ……}

  上述的DiscoveryClient构造函数主要实现了:

  1. 首先,是完成了一些属性对象的初始化工作,比如healthCheckHandlerProvider、applicationInfoManager、eurekaTransport、scheduler、heartbeatExecutor、cacheRefreshExecutor等。
  2. 然后,根据配置文件中fetchRegistry的配置项(默认为true),通过调用fetchRegistry()方法,实现Eureka服务端注册信息的拉去,该方法中又分为全量和增量两种方式。
  3. 当通过fetchRegistry()方法拉去失败后,会调用fetchRegistryFromBackup()方法,从缓存中获取注册信息
  4. 如果通过fetchRegistryFromBackup()方法也拉去信息失败,且要求必须在初始化时进行信息的拉去(通过shouldEnforceFetchRegistryAtInit配置,默认为false),则会抛出对应的异常。
  5. 提供了注册实例前的回调入口,可以进行功能增强。主要通过实现preRegistrationHandler.beforeRegistration()方法进行。
  6. 判断是否在客户端启动时,就把当前实例注册到Eureka服务,由registerWithEureka(默认true)和shouldEnforceRegistrationAtInit(默认false)两个配置决定,所以默认配置下,不会在启动时进行服务注册。
  7. 然后,调用initScheduledTasks()方法,该方法初始化了,一系列的定时任务,包括心跳、服务发现、服务注册等。
  8. 最后,调用Monitors.registerObject(this)方法,注册监听器。
4.4、CloudEurekaClient类

  CloudEurekaClient类是有SpringCloud提供的,继承了Netflix提供的DiscoveryClient类。

  在CloudEurekaClient类中,主要增强了一下能力:

  1. 提供了获取InstanceInfo对象的方法
  2. 提供了获取EurekaHttpClient对象的方法
  3. 提供了服务实例对象状态更新和删除重写状态的方法
  4. 重写了父类的onCacheRefreshed()方法,在原来的基础上,增加了HeartbeatEvent事件的处理。

5、总结

  在这篇内容中,我们只是简单的了解DiscoveryClient接口和类的层级结构,尤其是Netflix框架提供的DiscoveryClient类是非常复杂的,我们这里只是简单的学习了它构造函数的处理逻辑,其中还涉及了很多的细节,比如服务发现、服务注册等。后续我们在学习过程中,逐步学习DiscoveryClient类中的方法。

SpringCloud源码学习笔记之Eureka客户端——DiscoveryClient接口的层级结构相关推荐

  1. 雷神FFMpeg源码学习笔记

    雷神FFMpeg源码学习笔记 文章目录 雷神FFMpeg源码学习笔记 读取编码并依据编码初始化内容结构 每一帧的视频解码处理 读取编码并依据编码初始化内容结构 在开始编解码视频的时候首先第一步需要注册 ...

  2. RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的?

    RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 文章目录 RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 前言 项目 ...

  3. Java多线程之JUC包:Semaphore源码学习笔记

    若有不正之处请多多谅解,并欢迎批评指正. 请尊重作者劳动成果,转载请标明原文链接: http://www.cnblogs.com/go2sea/p/5625536.html Semaphore是JUC ...

  4. Vuex 4源码学习笔记 - 通过Vuex源码学习E2E测试(十一)

    在上一篇笔记中:Vuex 4源码学习笔记 - 做好changelog更新日志很重要(十) 我们学到了通过conventional-changelog来生成项目的Changelog更新日志,通过更新日志 ...

  5. Vuex 4源码学习笔记 - Vuex是怎么与Vue结合?(三)

    在上一篇笔记中:Vuex源码学习笔记 - Vuex开发运行流程(二) 我们通过运行npm run dev命令来启动webpack,来开发Vuex,并在Vuex的createStore函数中添加了第一个 ...

  6. jquery源码学习笔记三:jQuery工厂剖析

    jquery源码学习笔记二:jQuery工厂 jquery源码学习笔记一:总体结构 上两篇说过,query的核心是一个jQuery工厂.其代码如下 function( window, noGlobal ...

  7. Apache log4j-1.2.17源码学习笔记

    (1)Apache log4j-1.2.17源码学习笔记 http://blog.csdn.net/zilong_zilong/article/details/78715500 (2)Apache l ...

  8. PHP Yac cache 源码学习笔记

    YAC 源码学习笔记 本文地址 http://blog.csdn.net/fanhengguang_php/article/details/54863955 config.m4 检测系统共享内存支持情 ...

  9. Vuex 4源码学习笔记 - 通过dispatch一步步来掌握Vuex整个数据流(五)

    在上一篇笔记中:Vuex 4源码学习笔记 - Store 构造函数都干了什么(四) 我们通过查看Store 构造函数的源代码可以看到主要做了三件事情: 初始化一些内部变量以外 执行installMod ...

最新文章

  1. SQL Server审计功能入门:CDC(Change Data Capture)
  2. Android使用SQLite数据库(1)
  3. C++ Primer 第9章 顺序容器 第一次学习笔记
  4. Anaconda 安装操作及遇到的坑
  5. 支付宝打年兽神器 v1.4.5
  6. Cookie工具类-cookieUtil.js
  7. Windows Phone开发(18):变形金刚第九季——变换 转:http://blog.csdn.net/tcjiaan/article/details/7385056...
  8. Form.close与Application.Exit()的区别
  9. 【产品介绍】“弹性裸金属服务器”到底有那些特性?
  10. wpewebkit在ubuntu18.04上编译配置
  11. 世界500强面试题(经典)
  12. 5, Swift tableView点击收起展开的效果
  13. 基于MIPS指令集的单周期处理器设计
  14. 苹果mac系统在文件栏添加文件夹的方法
  15. swift网络请求封装(Moya)
  16. log4j2.xsml配置文件详细
  17. 【go 科学计算】用于统计、机器学习和数据操作
  18. 原生js的JSONP跨域请求
  19. 2019年最新的一波手机APP分享,每一个都是大家的菜哦!
  20. 为什么p元素内不能嵌套Div元素(随笔)

热门文章

  1. oracle特殊字符转码,URL转码方法及不能被转码的特殊字符
  2. Matlab中readtable用法
  3. Setup和Hold(Max/Min)分析
  4. HomeIDC的核心:购买顶级域名+开源自动域名解析+公网映射的保姆级教程
  5. [PHP] pow指数运算函数与二进制
  6. C++ 排序函数 sort(),qsort()的用法
  7. 使用PACKET_MMAP + PF_PACKET实现ZERO COPY抓包和发包
  8. 移动端网页实现拨打电话功能的几种方法
  9. 回归系数t检验公式_如何用分组回归检验调节作用?
  10. 戴尔PowerEdge R730服务器通过U盘启动的方式安装Windows Server 2016