注册中心 Eureka 源码解析 —— 应用实例注册发现(五)之过期

栏目: Java · 发布时间: 8年前

内容简介:注册中心 Eureka 源码解析 —— 应用实例注册发现(五)之过期

摘要: 原创出处 http://www.iocoder.cn/Eureka/instance-registry-evict/ 「芋道源码」欢迎转载,保留摘要,谢谢!

本文主要基于 Eureka 1.8.X 版本

注册中心 Eureka 源码解析 —— 应用实例注册发现(五)之过期

关注 微信公众号:【芋道源码】 有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有 源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言 将得到 认真 回复。 甚至不知道如何读源码也可以请教噢
  4. 新的 源码解析文章 实时 收到通知。 每周更新一篇左右
  5. 认真的 源码交流微信群。

1. 概述

本文主要分享 Eureka-Server 过期超时续租的租约

推荐 Spring Cloud 书籍:

2. 为什么需要过期

正常情况下,应用实例下线时候会主动向 Eureka-Server 发起下线请求。但实际情况下,应用实例可能异常崩溃,又或者是网络异常等原因,导致下线请求无法被成功提交。

介于这种情况,通过 Eureka-Client 心跳延长租约,配合 Eureka-Server 清理超时的租约解决上述异常。

3. EvictionTask

com.netflix.eureka.registry.AbstractInstanceRegistry.EvictionTask ,清理租约过期任务。在 Eureka-Server 启动时,初始化 EvictionTask 定时执行,实现代码如下:

// AbstractInstanceRegistry.java
/**
* 清理租约过期任务
*/
private final AtomicReference<EvictionTask> evictionTaskRef = new AtomicReference<EvictionTask>();

protected void postInit(){
 // .... 省略无关代码

 // 初始化 清理租约过期任务
 if (evictionTaskRef.get() != null) {
 evictionTaskRef.get().cancel();
 }
 evictionTaskRef.set(new EvictionTask());
 evictionTimer.schedule(evictionTaskRef.get(),
 serverConfig.getEvictionIntervalTimerInMs(),
 serverConfig.getEvictionIntervalTimerInMs());
}
  • 配置 eureka.evictionIntervalTimerInMs ,清理租约过期任务执行频率,单位:毫秒。默认,60000 毫秒。
  • EvictionTask 实现代码如下:

    class EvictionTask extends TimerTask{
    
     @Override
     public void run(){
     try {
     // 获取 补偿时间毫秒数
     long compensationTimeMs = getCompensationTimeMs();
     logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);
     // 清理过期租约逻辑
     evict(compensationTimeMs);
     } catch (Throwable e) {
     logger.error("Could not run the evict task", e);
     }
     }
    
    }
    
    • 调用 #compensationTimeMs() 方法,获得补偿时间毫秒数。计算公式 = 当前时间 - 最后任务执行时间 - 任务执行频率。为什么需要补偿时间毫秒数,在 「4. 过期逻辑」 Lease#isisExpired(additionalLeaseMs) 方法 揭晓。 #compensationTimeMs() 实现代码如下:

      /**
      * 最后任务执行时间
      */
      private final AtomicLong lastExecutionNanosRef = new AtomicLong(0L);
      
      long getCompensationTimeMs(){
       long currNanos = getCurrentTimeNano();
       long lastNanos = lastExecutionNanosRef.getAndSet(currNanos);
       if (lastNanos == 0L) {
       return 0L;
       }
       long elapsedMs = TimeUnit.NANOSECONDS.toMillis(currNanos - lastNanos);
       long compensationTime = elapsedMs - serverConfig.getEvictionIntervalTimerInMs();
       return compensationTime <= 0L ? 0L : compensationTime;
      }
      
      • 由于 JVM GC ,又或是时间偏移( clock skew ) 等原因,定时器执行实际比预期会 略有延迟 。笔者在本机 低负载 运行,大概 10 ms 内。

        compute a compensation time defined as the actual time this task was executed since the prev iteration, vs the configured amount of time for execution. This is useful for cases where changes in time (due to clock skew or gc for example) causes the actual eviction task to execute later than the desired time according to the configured cycle.
        
    • 调用 #evict(compensationTime) 方法,执行清理过期租约逻辑,在详细解析。

4. 过期逻辑

调用 #evict(compensationTime) 方法,执行清理过期租约逻辑,实现代码如下:

 1: public void evict(long additionalLeaseMs){
 2: logger.debug("Running the evict task");
 3: 
 4: if (!isLeaseExpirationEnabled()) {
 5: logger.debug("DS: lease expiration is currently disabled.");
 6: return;
 7: }
 8: 
 9: // 获得 所有过期的租约
10: // We collect first all expired items, to evict them in random order. For large eviction sets,
11: // if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it,
12: // the impact should be evenly distributed across all applications.
13: List<Lease<InstanceInfo>> expiredLeases = new ArrayList<>();
14: for (Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry : registry.entrySet()) {
15: Map<String, Lease<InstanceInfo>> leaseMap = groupEntry.getValue();
16: if (leaseMap != null) {
17: for (Entry<String, Lease<InstanceInfo>> leaseEntry : leaseMap.entrySet()) {
18: Lease<InstanceInfo> lease = leaseEntry.getValue();
19: if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) { // 过期
20: expiredLeases.add(lease);
21: }
22: }
23: }
24: }
25: 
26: // 计算 最大允许清理租约数量
27: // To compensate for GC pauses or drifting local time, we need to use current registry size as a base for
28: // triggering self-preservation. Without that we would wipe out full registry.
29: int registrySize = (int) getLocalRegistrySize();
30: int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
31: int evictionLimit = registrySize - registrySizeThreshold;
32: 
33: // 计算 清理租约数量
34: int toEvict = Math.min(expiredLeases.size(), evictionLimit);
35: if (toEvict > 0) {
36: logger.info("Evicting {} items (expired={}, evictionLimit={})", toEvict, expiredLeases.size(), evictionLimit);
37: 
38: // 逐个过期
39: Random random = new Random(System.currentTimeMillis());
40: for (int i = 0; i < toEvict; i++) {
41: // Pick a random item (Knuth shuffle algorithm)
42: int next = i + random.nextInt(expiredLeases.size() - i);
43: Collections.swap(expiredLeases, i, next);
44: Lease<InstanceInfo> lease = expiredLeases.get(i);
45: 
46: String appName = lease.getHolder().getAppName();
47: String id = lease.getHolder().getId();
48: EXPIRED.increment();
49: logger.warn("DS: Registry: expired lease for {}/{}", appName, id);
50: internalCancel(appName, id, false);
51: }
52: }
53: }
  • 第 3 至 7 行 :判断允许执行清理过期租约逻辑,主要和 自我保护机制 有关,在 《Eureka 源码解析 —— 应用实例注册发现(四)之自我保护机制》 有详细解析。
  • 第 9 至 24 行 :获得 所有过期 的租约集合。

    • 第 19 行 :调用 Lease#isisExpired(additionalLeaseMs) 方法,判断租约是否过期,实现代码如下:

      // Lease.java
      public boolean isExpired(long additionalLeaseMs){
       return (evictionTimestamp > 0 || System.currentTimeMillis() > (lastUpdateTimestamp + duration + additionalLeaseMs));
      }
      
      public void renew(){
       lastUpdateTimestamp = System.currentTimeMillis() + duration;
      }
      
      • :smiling_imp: 注意 :在不考虑 additionalLeaseMs 参数的情况下,租约过期时间比预期多了 一个 duration ,原因在于 #renew() 方法错误的设置 lastUpdateTimestamp = System.currentTimeMillis() + duration ,正确的设置应该是 lastUpdateTimestamp = System.currentTimeMillis()

        Note that due to renew() doing the ‘wrong” thing and setting lastUpdateTimestamp to +duration more than what it should be, the expiry will actually be 2 duration. *This is a minor bug and should only affect instances that ungracefully shutdown. Due to possible wide ranging impact to existing usage, this will not be fixed .

      • TODO[0023]:additionalLeaseMs

  • 第 26 至 34 行 :计算 最大允许 清理租约的数量,后计算允许清理租约的数量。

    • :smiling_imp: 注意 :即使 Eureka-Server 关闭 自我保护机制 ,如果使用 renewalPercentThreshold = 0.85 默认配置,结果会是 分批逐步 过期。举个例子:

      // 假设 20 个租约,其中有 10 个租约过期。
      
      // 第一轮执行开始
      int registrySize = 20;
      int registrySizeThreshold = (int) (20 * 0.85) = 17;
      int evictionLimit = 20 - 17 = 3;
      int toEvict = Math.min(10, 3) = 3;
      // 第一轮执行结束,剩余 17 个租约,其中有 7 个租约过期。
      
      // 第二轮执行开始
      int registrySize = 17;
      int registrySizeThreshold = (int) (17 * 0.85) = 14;
      int evictionLimit = 17 - 14 = 3;
      int toEvict = Math.min(7, 3) = 3;
      // 第二轮执行结束,剩余 14 个租约,其中有 4 个租约过期。
      
      // 第三轮执行开始
      int registrySize = 14;
      int registrySizeThreshold = (int) (14 * 0.85) = 11;
      int evictionLimit = 14 - 11 = 3;
      int toEvict = Math.min(4, 3) = 3;
      // 第三轮执行结束,剩余 11 个租约,其中有 1 个租约过期。
      
      // 第四轮执行开始
      int registrySize = 11;
      int registrySizeThreshold = (int) (11 * 0.85) = 9;
      int evictionLimit = 11 - 9 = 2;
      int toEvict = Math.min(1, 2) = 1;
      // 第四轮执行结束,剩余 10 个租约,其中有 0 个租约过期。结束。
      
      • 结论:是否开启自我保护的差别,在于是否执行清理过期租约逻辑。如果想关闭 分批逐步 过期,设置 renewalPercentThreshold = 0
    • 由于 JVM GC ,或是本地时间差异原因,可能自我保护机制的阀值 expectedNumberOfRenewsPerMinnumberOfRenewsPerMinThreshold 不够正确,在 过期 这个相对“危险”的操作, 重新计算自我保护 的阀值。

  • 第 35 至 51 行 : 随机 清理过期的租约。由于租约是按照 应用顺序 添加到数组,通过随机的方式, 尽量避免单个应用被全部过期

  • 第 50 行 :调用 #internalCancel() 方法,下线已过期的租约,在 《Eureka 源码解析 —— 应用实例注册发现(四)之自我保护机制》「3.2 下线应用实例信息」 有详细解析。

666. 彩蛋

:tired_face: 原本觉得比较容易的一篇文章,结果消耗了比想象中的时间,可能有四个小时。主要卡在补偿时间,目前也没弄懂。如果有知道的胖友,麻烦告知下。

胖友,分享我的公众号( 芋道源码 ) 给你的胖友可好?


以上所述就是小编给大家介绍的《注册中心 Eureka 源码解析 —— 应用实例注册发现(五)之过期》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据压缩导论

数据压缩导论

萨尤得 / 2009-2 / 99.00元

《数据压缩导论(英文版·第3版)》是数据压缩方面的经典著作,介绍了各种类型的压缩模式。书中首先介绍了基本压缩方法(包括无损压缩和有损压缩)中涉及的数学知识,为常见的压缩形式打牢了信息论基础,然后从无损压缩体制开始,依次讲述了霍夫曼编码、算术编码以及字典编码技术等,对于有损压缩,还讨论了使用量化的模式,描述了标量、矢量以及微分编码和分形压缩技术,最后重点介绍了视频加密。《数据压缩导论(英文版·第3版......一起来看看 《数据压缩导论》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

SHA 加密
SHA 加密

SHA 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换