ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

9、Eureka 源码解析 之 Eureka Server 服务过期

2021-01-06 23:31:49  阅读:211  来源: 互联网

标签:续约 实例 过期 Server 源码 time Eureka


下面是官方提供的 Eureka 架构图:
在这里插入图片描述

1、Eureka Server 为什么要服务过期

正常情况下,应用实例下线时候会主动向 Eureka-Server 发起下线请求,也就是我们之前分析的 – 8、Eureka 源码解析 之 Eureka Client 服务下线。但实际情况下,应用实例可能异常崩溃,又或者是网络异常等原因,导致下线请求无法被成功提交。

这种情况之后,需要 Eureka Client 定时向 Eureka Server 发送续约配合 Eureka Client 通过定时任务清理超时的租约解决上述异常。

2、EvictionTask

EvictionTask 是清理租约过期任务,下面是它的调用时序图:
在这里插入图片描述
当 Eureka Server 启动的时候就会调用实现了 Java Servlet 规范 ServletContextListener 监听器的 EurekaBootStrap 到调用初始化注册服务 AbstractInstanceRegistry。然后会启用以下几个定时任务:

  • MeasuredRate,统计定时任务,在 AbstractInstanceRegistry 的构建器创建 MeasuredRate 对象的时候传入 1000 * 60 * 1,然后在这里调用它的 start 方法里面有一个定时任务,每隔 60 秒也就是每隔 1 分钟执行一次。这个定时任务里面有 2 个 AtomicLong 类型的参数。一个是 AtomicLong currentBucket 每进行一次续约的时候就会调用它 + 1,另一个是AtomicLong lastBucketMeasuredRate任务每分钟进行执行的时候就会把AtomicLong currentBucket 里面的值设置到AtomicLong lastBucket当中去,然后把AtomicLong currentBucket值清空。然后通过获取 AtomicLong lastBucket 的值就能够得到最近一分钟续约的次数(最近一分钟续约的次数这个在后面要讲的 Eureka Server 自我保护机制当中会使用到)
  • 另外一个就是定时调用 EvictionTask 任务,过期注册表中超时续约的应用。默认 60 秒执行一次

3、服务过期

下面我们来分析一下服务过期的逻辑:

EvictionTask

    class EvictionTask extends TimerTask {

        private final AtomicLong lastExecutionNanosRef = new AtomicLong(0l);

        @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);
            }
        }

        /**
         * 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.
         */
        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;
        }

        long getCurrentTimeNano() {  // for testing
            return System.nanoTime();
        }

    }

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.

首先它会调用 getCompensationTimeMs 方法计算一个补偿时间,由于 JVM GC ,又或是时间偏移( clock skew ) 等原因,定时器执行实际比预期会略有延迟。

传入计算获取到的补偿时间,调用 evict 方法过期超时续约的应用。

AbstractInstanceRegistry#evict

    public void evict(long additionalLeaseMs) {
        logger.debug("Running the evict task");

        if (!isLeaseExpirationEnabled()) {
            logger.debug("DS: lease expiration is currently disabled.");
            return;
        }

        // We collect first all expired items, to evict them in random order. For large eviction sets,
        // if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it,
        // the impact should be evenly distributed across all applications.
        List<Lease<InstanceInfo>> expiredLeases = new ArrayList<>();
        for (Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry : registry.entrySet()) {
            Map<String, Lease<InstanceInfo>> leaseMap = groupEntry.getValue();
            if (leaseMap != null) {
                for (Entry<String, Lease<InstanceInfo>> leaseEntry : leaseMap.entrySet()) {
                    Lease<InstanceInfo> lease = leaseEntry.getValue();
                    if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) {
                        expiredLeases.add(lease);
                    }
                }
            }
        }

        // To compensate for GC pauses or drifting local time, we need to use current registry size as a base for
        // triggering self-preservation. Without that we would wipe out full registry.
        int registrySize = (int) getLocalRegistrySize();
        int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
        int evictionLimit = registrySize - registrySizeThreshold;

        int toEvict = Math.min(expiredLeases.size(), evictionLimit);
        if (toEvict > 0) {
            logger.info("Evicting {} items (expired={}, evictionLimit={})", toEvict, expiredLeases.size(), evictionLimit);

            Random random = new Random(System.currentTimeMillis());
            for (int i = 0; i < toEvict; i++) {
                // Pick a random item (Knuth shuffle algorithm)
                int next = i + random.nextInt(expiredLeases.size() - i);
                Collections.swap(expiredLeases, i, next);
                Lease<InstanceInfo> lease = expiredLeases.get(i);

                String appName = lease.getHolder().getAppName();
                String id = lease.getHolder().getId();
                EXPIRED.increment();
                logger.warn("DS: Registry: expired lease for {}/{}", appName, id);
                internalCancel(appName, id, false);
            }
        }
    }
  • 首先遍历注册表注册的应用列表,找到所有续约过期的应用实例列表。过期判断条件是:当前时间 > 最后一次续约时间 + 90 秒 + 补偿时间。
  • 然后调用 getLocalRegistrySize 获取当前注册列表中应用实例的个数
  • 接着通过下面的公式获取一个期望过期的个数 evictionLimit。evictionLimit = 注册列表所有应用实例的个数 - 注册列表所有应用实例的个数 * 获得续订的最小百分比(默认 0.85)。也就是:期望过期的个数 = 应用实例 * 0.15。如果有20 个服务应用那么这个值就是 3.
  • 然后取第一步获取到续约过期的应用实例个数与期望过期的应用实例个数的最小值
  • 如果这个最小值大于 0,就随机过期续约过期的应用实例列表里面的应用实例并且调用内部取消逻辑(之前服务下线分析过)

如果 Eureka Client 默认会每 30 秒发送心跳到 Eureka Server。如果 Eureka Server 在进行服务过期判断的时候也就是:最后一次续约时间 + 90 秒 + 补偿时间小于系统当前时间就会以服务进行过期。

有以下两点需要注意一下:

  • Eureka Client 在进行续约的时候是把 lastUpdateTimestamp 设置成系统的当前时间 + 90 秒。然后 Eureka Server 在进行服务过期判断的时候是:最后一次续约时间 + 90 秒 + 补偿时间小于系统当前时间就会以服务进行过期。最后在 Eureka Client 拉取注册表是每 30 秒进行拉取。所以一个服务不可用时其它服务需要感知到这个服务不可用就需要:90 秒 + 90 秒 + 30 秒 = 210秒,也就是 3 分半 才能感知到
  • Eureka Server 在服务过期的时候会根据:应用实例 * 0.15 与 心跳过期应用个数随机顺序将它们剔除出去。因为对于需要大量剔除服务应用时,如果我们不这样做,我们可能会在 Eureka 自我保护开始之前清除整个应用程序。通过随机化,影响应该均匀分布在所有应用程序中。

标签:续约,实例,过期,Server,源码,time,Eureka
来源: https://blog.csdn.net/u012410733/article/details/112298750

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有