Eureka 源码解析 —— EndPoint 与 解析器
来源:互联网 发布:mac上安装jmeter 编辑:程序博客网 时间:2024/06/05 05:56
摘要: 原创出处 http://www.iocoder.cn/Eureka/end-point-and-resolver/ 「芋道源码」欢迎转载,保留摘要,谢谢!
本文主要基于 Eureka 1.8.X 版本
- 1. 概述
- 2. EndPoint
- 2.1 EurekaEndpoint
- 2.2 DefaultEndpoint
- 2.3 AwsEndpoint
- 3. 解析器
- 3.1 ClusterResolver
- 3.2 ClosableResolver
- 3.3 DnsTxtRecordClusterResolver
- 3.4 ConfigClusterResolver
- 3.5 ZoneAffinityClusterResolver
- 3.6 AsyncResolver
- 3.6.1 定时任务
- 3.6.2 解析 EndPoint 集群
- 4. 初始化解析器
- EndPoint ,服务端点。例如,Eureka-Server 的访问地址。
- EndPoint 解析器,将配置的 Eureka-Server 的访问地址解析成 EndPoint 。
- 第一种,直接配置实际访问地址。例如,
eureka.serviceUrl.defaultZone=http://127.0.0.1:8080/v2
。 - 第二种,基于 DNS 解析出访问地址。例如,
eureka.shouldUseDns=true
并且eureka.eurekaServer.domainName=eureka.iocoder.cn
。 - 红色部分 —— EndPoint
- 黄色部分 —— EndPoint 解析器
- 请支持正版。下载盗版,等于主动编写低级 BUG 。
- 程序猿DD —— 《Spring Cloud微服务实战》
- 周立 —— 《Spring Cloud与Docker微服务架构实战》
- 两书齐买,京东包邮。
- 重写了
#equals(...)
和#hashCode(...)
方法,标准实现方式,这里就不贴代码了。 - 重写了
#compareTo(...)
方法,基于serviceUrl
属性做比较。 - 重写了
#equals(...)
和#hashCode(...)
方法,标准实现方式,这里就不贴代码了。 DnsTxtRecordClusterResolver 通过集群根地址(
rootClusterDNS
) 解析出 EndPoint 集群。需要在 DNS 配置两层解析记录:- 第一层 :
- 主机记录 :格式为
TXT.${REGION}.${自定义二级域名}
。 - 记录类型 :TXT 记录类型。
- 记录值 :第二层的主机记录。如有多个第二层级,使用空格分隔。
- 主机记录 :格式为
- 第二层:
- 主机记录 :格式为
TXT.${ZONE}.${自定义二级域名}
或者${ZONE}.${自定义二级域名}
。 - 记录类型 :TXT 记录类型。
- 记录值 :EndPoint 的网络地址。如有多个 EndPoint,使用空格分隔。
- 举个例子:
- 举个例子:
- 主机记录 :格式为
- 第一层 :
rootClusterDNS
,集群根地址。例如:txt.default.eureka.iocoder.cn
,其·txt.default.eureka
为 DNS 解析记录的第一层的主机记录。region
:地区。需要和rootClusterDNS
的${REGION}
一致。extractZoneFromDNS
:是否解析 DNS 解析记录的第二层级的主机记录的${ZONE}
可用区。第 12 至 16 行 :调用
#resolve(rootClusterDNS)
解析第一层 DNS 记录。实现代码如下:- 第 4 行 : 调用
DnsResolver#getCNamesFromTxtRecord(...)
方法,解析 TXT 主机记录。点击链接查看带中文注释的 DnsResolver 的代码,比较解析,笔者就不啰嗦了。 - 第 5 至 8 行 :当传递参数
rootClusterDNS
不以txt.
开头时,即使第 4 行解析成功,也会报错,此时是个 Eureka 的 BUG 。因此,配置 DNS 解析记录时,主机记录暂时必须以txt.
开头。
- 第 4 行 : 调用
第 17 至 25 行 :循环第一层 DNS 记录的解析结果,进一步解析第二层 DNS 记录。
- 第 20 行 :解析可用区(
zone
)。 - 第 21 行 :调用
#resolve(rootClusterDNS)
解析第二层 DNS 记录。
- 第 20 行 :解析可用区(
第 3 至 8 行 :基于 DNS 获取 EndPoint 集群,调用
#getClusterEndpointsFromDns()
方法,实现代码如下:- 必须配置
eureka.shouldUseDns=true
,开启基于 DNS 获取 EndPoint 集群。 - 必须配置
eureka.eurekaServer.domainName=${xxxxx}
,配置集群根地址。 - 选填配
eureka.eurekaServer.port
,eureka.eurekaServer.context
。 - 从代码中我们可以看出,使用 DnsTxtRecordClusterResolver 解析出 EndPoint 集群。
- 必须配置
第 9 至 13 行 :直接配置文件填写实际 EndPoint 集群,调用
#getClusterEndpointsFromConfig()
方法,实现代码如下:- 第 3 行 :获得可用区数组。通过
eureka.${REGION}.availabilityZones
配置。 - 第 5 行 :调用
InstanceInfo#getZone(...)
方法,获得应用实例自己所在的可用区(zone
)。非亚马逊 AWS 环境下,可用区数组的第一个元素就是应用实例自己所在的可用区。 第 7 行 :调用
EndpointUtils#getServiceUrlsMapFromConfig(...)
方法,获得可用区与serviceUrls
的映射。实现代码如下:第 13 行 :获得开始位置。实现代码如下:
- 当方法参数
preferSameZone=true
,即eureka.preferSameZone=true
( 默认值 :true
) 时,开始位置为可用区数组(availZones
)的第一个和应用实例所在的可用区(myZone
)【相等】元素的位置。 - 当方法参数
preferSameZone=false
,即eureka.preferSameZone=false
( 默认值 :true
) 时,开始位置为可用区数组(availZones
)的第一个和应用实例所在的可用区(myZone
)【不相等】元素的位置。
- 当方法参数
第 20 至 33 行 :从开始位置顺序将剩余的可用区的
serviceUrls
添加到结果。顺序理解如下图:
第 9 至 18 行 :拼装 EndPoint 集群结果。
- 属性
delegate
,委托的解析器。目前代码里使用的是 ConfigClusterResolver 。 - 属性
zoneAffinity
,是否可用区亲和。true
:EndPoint 可用区为本地的优先被放在前面。false
:EndPoint 可用区非本地的优先被放在前面。
- 第 2 行 :调用
ClusterResolver#getClusterEndpoints()
方法,获得 EndPoint 集群。再调用ResolverUtils#splitByZone(...)
方法,拆分成本地和非本地的可用区的 EndPoint 集群,点击链接查看实现。 第 8 行 :调用
#randomizeAndMerge(...)
方法,分别随机打乱每个 EndPoint 集群,并进行合并数组,实现代码如下:- 注意,
ResolverUtils#randomize(...)
使用以本机IP为随机种子,有如下好处:- 多个主机,实现对同一个 EndPoint 集群负载均衡的效果。
- 单个主机,同一个 EndPoint 集群按照固定顺序访问。Eureka-Server 不是强一致性的注册中心,Eureka-Client 对同一个 Eureka-Server 拉取注册信息,保证两者之间增量同步的一致性。
- 注意,
第 10 至 12 行 :非可用区亲和,将非本地的可用区的 EndPoint 集群放在前面。
backgroundTask
,后台任务,定时解析 EndPoint 集群。- TimedSupervisorTask ,在 《Eureka 源码解析 —— 应用实例注册发现(二)之续租》「2.3 TimedSupervisorTask」 有详细解析。
updateTask
实现代码如下:delegate
,委托的解析器,目前代码为 ZoneAffinityClusterResolver。
后台任务的发起在
#getClusterEndpoints()
方法,在 「3.6.2 解析 EndPoint 集群」 详细解析。
第 5 至 9 行 :若未预热解析 EndPoint 集群结果,调用
#doWarmUp()
方法,进行预热。若预热失败,取消定时任务的第一次延迟。#doWarmUp()
方法实现代码如下:- 调用
updateTask
,解析 EndPoint 集群。
- 调用
第 10 至 13 行 : 若未调度定时任务,进行调度,调用
#scheduleTask()
方法,实现代码如下:
- x
第 15 行 :返回 EndPoint 集群。当第一次预热失败,会返回空,直到定时任务获得到结果。
调用
EurekaHttpClients#newBootstrapResolver(...)
方法,创建 EndPoint 解析器,实现代码如下:
1. 概述
本文主要分享 EndPoint 与 解析器。
目前有多种 Eureka-Server 访问地址的配置方式,本文只分享 Eureka 1.x 的配置,不包含 Eureka 1.x 对 Eureka 2.x 的兼容配置:
本文涉及类在 com.netflix.discovery.shared.resolver
包下,涉及到主体类的类图如下( 打开大图 ):
推荐 Spring Cloud 书籍:
2. EndPoint
2.1 EurekaEndpoint
com.netflix.discovery.shared.resolver.EurekaEndpoint
,Eureka 服务端点接口,实现代码如下:
2.2 DefaultEndpoint
com.netflix.discovery.shared.resolver.DefaultEndpoint
,默认 Eureka 服务端点实现类。实现代码如下:
2.3 AwsEndpoint
com.netflix.discovery.shared.resolver.aws.AwsEndpoint
,基于 region
、zone
的 Eureka 服务端点实现类 ( 请不要在意 AWS 开头 )。实现代码如下:
3. 解析器
EndPoint 解析器使用委托设计模式实现。所以,上文图片中我们看到好多个解析器,实际代码非常非常非常清晰。
FROM 《委托模式》
委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。委托模式是一项基本技巧,许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式。委托模式使得我们可以用聚合来替代继承,它还使我们可以模拟mixin。
我们在上图的基础上,增加委托的关系,如下图:
3.1 ClusterResolver
com.netflix.discovery.shared.resolver.ClusterResolver
,集群解析器接口。接口代码如下:
3.2 ClosableResolver
com.netflix.discovery.shared.resolver.ClosableResolver
,可关闭的解析器接口,继承自 ClusterResolver 接口。接口代码如下:
3.3 DnsTxtRecordClusterResolver
com.netflix.discovery.shared.resolver.aws.DnsTxtRecordClusterResolver
,基于 DNS TXT 记录类型的集群解析器。类属性代码如下:
#getClusterEndpoints(...)
方法,实现代码如下:
3.4 ConfigClusterResolver
com.netflix.discovery.shared.resolver.aws.ConfigClusterResolver
,基于配置文件的集群解析器。类属性代码如下:
#getClusterEndpoints(...)
方法,实现代码如下:
3.5 ZoneAffinityClusterResolver
com.netflix.discovery.shared.resolver.aws.ZoneAffinityClusterResolver
,使用可用区亲和的集群解析器。类属性代码如下:
#getClusterEndpoints(...)
方法,实现代码如下:
3.6 AsyncResolver
com.netflix.discovery.shared.resolver.AsyncResolver
,异步执行解析的集群解析器。AsyncResolver 属性较多,而且复杂的多,我们拆分到具体方法里分享。
3.6.1 定时任务
AsyncResolver 内置定时任务,定时刷新 EndPoint 集群解析结果。
为什么要刷新?例如,Eureka-Server 的 serviceUrls
基于 DNS 配置。
定时任务代码如下:
3.6.2 解析 EndPoint 集群
调用 #getClusterEndpoints()
方法,解析 EndPoint 集群,实现代码如下:
4. 初始化解析器
Eureka-Client 在初始化时,调用 DiscoveryClient#scheduleServerEndpointTask()
方法,初始化 AsyncResolver 解析器。实现代码如下:
* 第 10 至 23 行 :组合解析器,用于 Eureka 1.x 对 Eureka 2.x 的兼容配置,暂时不需要了解。TODO[0028]写入集群和读取集群* 第 26 行 :调用 `#defaultBootstrapResolver()` 方法,创建默认的解析器 AsyncResolver 。* 第 40 至 45 行 :创建 ZoneAffinityClusterResolver 。在 ZoneAffinityClusterResolver 构造方法的参数,我们看到创建 ConfigClusterResolver 作为 `delegate` 参数。* 第 48 行 :调用 `ZoneAffinityClusterResolver#getClusterEndpoints()` 方法,**第一次 Eureka-Server EndPoint 集群解析**。* 第 51 至 55 行 :解析不到 Eureka-Server EndPoint 集群时,可以通过配置( `eureka.experimental.clientTransportFailFastOnInit=true` ),使 Eureka-Client 初始化失败。`#failFastOnInitCheck(...)` 方法,实现代码如下:
<figure class="highlight java"><table><tr><td class="code"><pre><div class="line"><span class="comment">// potential future feature, guarding with experimental flag for now</span></div><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">failFastOnInitCheck</span><span class="params">(EurekaClientConfig clientConfig, String msg)</span> </span>{</div><div class="line"> <span class="keyword">if</span> (<span class="string">"true"</span>.equals(clientConfig.getExperimental(<span class="string">"clientTransportFailFastOnInit"</span>))) {</div><div class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(msg);</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>* x
第 58 至 64 行 :创建 AsyncResolver 。从代码上,我们可以看到,
AsyncResolver.resultsRef
属性一开始已经用initialValue
传递给 AsyncResolver 构造方法。实现代码如下:
- x
- Eureka 源码解析 —— EndPoint 与 解析器
- Eureka 源码解析 —— Eureka-Server 集群同步
- Eureka 源码解析 —— 任务批处理
- Eureka 源码解析 —— 网络通信
- Eureka 源码解析 —— StringCache
- eureka客户端源码解析
- Eureka 源码解析 —— 项目结构简介
- Eureka 源码解析 —— 调试环境搭建
- Eureka 源码解析 —— 注册表 InstanceRegistry 类关系
- Eureka 源码解析 —— Eureka-Client 初始化(一)之 EurekaInstanceConfig
- Eureka 源码解析 —— Eureka-Client 初始化(二)之 EurekaClientConfig
- Eureka 源码解析 —— Eureka-Client 初始化(三)之 EurekaClient
- Eureka 源码解析 —— Eureka-Server 启动(一)之 ServerConfig
- Eureka 源码解析 —— Eureka-Server 启动(二)之 EurekaBootStrap
- Eureka 源码解析 —— Eureka源码解析 —— 应用实例注册发现 (九)之岁月是把萌萌的读写锁
- Eureka 源码解析 —— 应用实例注册发现(一)之注册
- Eureka 源码解析 —— 应用实例注册发现(二)之续租
- Eureka 源码解析 —— 应用实例注册发现(三)之下线
- Effective C++ 01
- 项目案例:二进制文件与十六进制字符串转化常用操作
- ZigBee中Profile(规范),Cluster(簇)
- 1*2*...100的积的递归和循环实现
- 孤儿进程与僵尸进程[总结]
- Eureka 源码解析 —— EndPoint 与 解析器
- springboot-jpa-thymeleaf
- Js实现unicode 中文互转
- 一台电脑运行多个tomcat方法
- 从0开始学习SpringCould(4)--SpringBoot 集成freemarker
- Windows下DATA目录的迁移
- ubuntu中如何用chrome浏览器播放本地flash文件
- gradle.properties的使用教程
- 深入理解WeakHashmap