编程语言
首页 > 编程语言> > 9、Eureka 源码解析 之 Eureka Server 服务过期

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

作者:互联网

下面是官方提供的 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。然后会启用以下几个定时任务:

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

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

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

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