点击上方“芋道源码”,选择“置顶公众号”
技术文章第一时间送达!
源码精品专栏
本文主要基于 Eureka 1.8.X 版本
1. 概述
2. 应用集合一致性哈希码
2.1 计算公式
2.2 合理性
3. Eureka-Client 发起增量获取
3.1 合并应用集合
4. Eureka-Server 接收全量获取
3.1 接收全量获取请求
3.2 最近租约变更记录队列
3.3 缓存读取
666. 彩蛋
1. 概述
本文主要分享 Eureka-Client 向 Eureka-Server 获取增量注册信息的过程。
前置阅读:《Eureka 源码解析 —— 应用实例注册发现(六)之全量获取》
FROM 《深度剖析服务发现组件Netflix Eureka》
Eureka-Client 获取注册信息,分成全量获取和增量获取。默认配置下,Eureka-Client 启动时,首先执行一次全量获取进行本地缓存注册信息,而后每 30 秒增量获取刷新本地缓存( 非“正常”情况下会是全量获取 )。
本文重点在于增量获取。
推荐 Spring Cloud 书籍:
请支持正版。下载盗版,等于主动编写低级 BUG 。
程序猿DD —— 《Spring Cloud微服务实战》
周立 —— 《Spring Cloud与Docker微服务架构实战》
两书齐买,京东包邮。
推荐 Spring Cloud 视频:
Java 微服务实践 - Spring Boot
Java 微服务实践 - Spring Cloud
Java 微服务实践 - Spring Boot / Spring Cloud
2. 应用集合一致性哈希码
Applications.appsHashCode
,应用集合一致性哈希码。
增量获取注册的应用集合( Applications ) 时,Eureka-Client 会获取到:
Eureka-Server 近期变化( 注册、下线 )的应用集合
Eureka-Server 应用集合一致性哈希码
Eureka-Client 将变化的应用集合和本地缓存的应用集合进行合并后进行计算本地的应用集合一致性哈希码。若两个哈希码相等,意味着增量获取成功;若不相等,意味着增量获取失败,Eureka-Client 重新和 Eureka-Server 全量获取应用集合。
Eureka 比较应用集合一致性哈希码,和日常我们通过哈希码比较两个对象是否相等类似。
2.1 计算公式
appsHashCode = ${status}_${count}_
使用每个应用实例状态(
status
) + 数量(count
)拼接出一致性哈希码。若数量为 0 ,该应用实例状态不进行拼接。状态以字符串大小排序。举个例子,8 个 UP ,0 个 DOWN ,则
appsHashCode = UP_8_
。8 个 UP ,2 个 DOWN ,则appsHashCode = DOWN_2_UP_8_
。实现代码如下:
// Applications.java
public String getReconcileHashCode() {
// 计数集合 key:应用实例状态
TreeMap<String, AtomicInteger> instanceCountMap = new TreeMap<String, AtomicInteger>();
populateInstanceCountMap(instanceCountMap);
// 计算 hashcode
return getReconcileHashCode(instanceCountMap);
}计数那块代码,使用 Integer 即可,无需使用 AtomicInteger 。
调用
#populateInstanceCountMap()
方法,计算每个应用实例状态的数量。实现代码如下:// Applications.java
public void populateInstanceCountMap(Map<String, AtomicInteger> instanceCountMap) {
for (Application app : this.getRegisteredApplications()) {
for (InstanceInfo info : app.getInstancesAsIsFromEureka()) {
// 计数
AtomicInteger instanceCount = instanceCountMap.computeIfAbsent(info.getStatus().name(),
k -> new AtomicInteger(0));
instanceCount.incrementAndGet();
}
}
}
public List<Application> getRegisteredApplications() {
return new ArrayList<Application>(this.applications);
}
// Applications.java
public List<InstanceInfo> getInstancesAsIsFromEureka() {
synchronized (instances) {
return new ArrayList<InstanceInfo>(this.instances);
}
}
调用
#getReconcileHashCode()
方法,计算hashcode
。实现代码如下:public static String getReconcileHashCode(Map<String, AtomicInteger> instanceCountMap) {
StringBuilder reconcileHashCode = new StringBuilder(75);
for (Map.Entry<String, AtomicInteger> mapEntry : instanceCountMap.entrySet()) {
reconcileHashCode.append(mapEntry.getKey()).append(STATUS_DELIMITER) // status
.append(mapEntry.getValue().get()).append(STATUS_DELIMITER); // count
}
return reconcileHashCode.toString();
}
2.2 合理性
本小节,建议你理解完全文后,再回到此处
本小节,建议你理解完全文后,再回到此处
本小节,建议你理解完全文后,再回到此处
笔者刚看完应用集合一致性哈希算法的计算公式,处于一脸懵逼的状态。这么精简的方式真的能够校验出数据的一致性么?不晓得有多少读者跟笔者有一样的疑惑。下面我们来论证该算法的合理性( 一本正经的胡说八道 )。
一致性哈希值通过状态 + 数量来计算,那么是不是可能状态总数是一样多,实际分布在不同的应用?那么我们列举模型如下:
UP | |
---|---|
应用A | m |
应用B | n |
如果此时应用A 下线了 c 个原应用实例,应用B 注册了 c 个信应用实例,那么处于 UP 状态的数量仍然是 m + n 个。
正常情况下,Eureka-Client 从 Eureka-Server 获取到完整的增量变化并合并,此时应用情况如下表格所示,两者是一致的,一致性哈希算法合理。
UP (server) | UP (client) | |
---|---|---|
应用A | m - c | m - c |
应用B | n + c | n + c |
异常情况下【1】,变更记录队列全部过期。那 Eureka-Client 从 Eureka-Server 获取到空的增量变化并合并,此时应用情况如下表格所示,两者应用是不相同的, 一致性哈希值却是相等的,一致性哈希算法不合理。
UP (server) | UP (client) | |
---|---|---|
应用A | m - c | m |
应用B | n + c | n |
异常情况下【2】,变更记录队列部分过期,例如应用A 和 应用B 都剩余 w 条变更记录。那 Eureka-Client 从 Eureka-Server 获取到部分的增量变化并合并,两者应用是不相同的,此时应用情况如下表格所示,一致性哈希值却是相等的,一致性哈希算法不合理。
UP (server) | UP (client) | |
---|---|---|
应用A | m - c | m - w |
应用B | n + c | n + w |
What ? 从异常情况【1】【2】可以看到,一致性哈希算法竟然是不合理的,那么我们手动来做一次最精简的实验。实验如下:
模拟场景:异常情况【1】,m = n = c = 1 。简单粗暴。
特别配置
eureka.retentionTimeInMSInDeltaQueue = 1
,变更记录队列每条记录存活时长 1 ms。用以实现 Eureka-Client 请求不到完整的增量变化。eureka.deltaRetentionTimerIntervalInMs = 1
,变更记录队列每条记录过期定时任务执行频率 1 ms。用以实现 Eureka-Client 请求不到完整的增量变化。eureka.shouldUseReadOnlyResponseCache = false
,禁用响应缓存的只读缓存。用以避免等待缓存刷新。eureka.waitTimeInMsWhenSyncEmpty = 1
,
实验过程
00:00 启动 Eureka-Server
00:30 启动应用A ,向 Eureka-Server 注册
01:00 启动 Eureka-Client ,向 Eureka-Server 获取注册信息,等待获取到应用A
01:30 关闭应用A 。立即启动应用B ,向 Eureka-Server 注册
等待 5 分钟,Eureka-Client 无法获取到应用B
此时应用情况如下表格所示,两者应用是不相同的,一致性哈希值却是相等的,一致性哈希算法不合理。
UP (server) | UP (client) | |
---|---|---|
应用A | 0 | 1 |
应用B | 1 | 0 |