eureka源码解析——服务故障自动剔除


前言

服务注册到eurekaServer上之后,会定时30s发送心跳请求来进行续约,如果服务宕机了或者其他情况导致没有主动向EurekaServer发送服务下线请求,那么EurekaServer在后台会启动一个定时后,默认60s扫描一次注册表,看看哪些服务实例规定时间没有发送续约请求,将这些故障实例从注册表剔除。


一、服务故障剔除源码解析

1.1、自动剔除定时任务启动

之前将EurekaServer启动的时候,在EurekaBootStrapinitEurekaServerContext方法中会调用registryopenForTraffic方法中,在openForTraffic方法中会调用super.postInit();方法也就是AbstractInstanceRegistry#postInit方法会启动定时任务

在这里插入图片描述

1.1.1、AbstractInstanceRegistry#postInit

这个方法中干了两件事:

  1. 调用renewsLastMin.start启动一个定时任务,用于统计每分钟的服务续约的renew统计
  2. 启动一个定时任务,用于做服务剔除的,默认60s执行一次
    protected void postInit() {
        renewsLastMin.start();
        if (evictionTaskRef.get() != null) {
            evictionTaskRef.get().cancel();
        }
        evictionTaskRef.set(new EvictionTask());
        //60s执行一次
        evictionTimer.schedule(evictionTaskRef.get(),
                serverConfig.getEvictionIntervalTimerInMs(),
                serverConfig.getEvictionIntervalTimerInMs());
    }

1.2、服务剔除流程

1.2.1、EvictionTask#run

这里会调用evict方法进行剔除操作

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

1.2.2、EvictionTask#evict

  1. 这里首先会判断是否触发了自我保护机制,触发机制如下 比如说每一个新的实例注册到Eureka ServerEureka 都会记录下来当前注册了多少个实例,再就是默认客户端每30s进行服务续约一次,一分钟1个实例就是2次服务续约,它都会记录下来,按照这个规则,如果我有10个实例注册到Eureka Server 上,那么按照正常流程下,每分钟就会有20个服务续约请求,当然这是最好的情况下,不好的情况下服务续约请求可能达不到20次,这个时候它就有一个最小阈值,也就是85% ,这里就是需要最少有15 次服务续约,当低于这个阈值的时候,就会自动开启自我保护机制,这次服务剔除就不会再进行。
  2. 遍历注册表,判断续约有没有到期,如果到期则进行剔除,这里剔除会走我们上文说到的服务下线的逻辑,并且不进行节点间的同步,这个因为可能存在网络分区,可能实例只是和当前节点的心跳断了。
    public void evict(long additionalLeaseMs) {
        logger.debug("Running the evict task");

        //自我保护机制,是否启用服务故障剔除
        /**
         * 自我保护机制触发流程, 比如说每一个新的实例注册到Eureka Server ,Eureka 都会记录下来当前注册了多少个实例,再就是默认客户端每30s进行服务续约一次,
         * 一分钟1个实例就是2次服务续约,它都会记录下来,按照这个规则,如果我有10个实例注册到了Eureka Server 上,那么按照正常流程下,每分钟就会有20个服务续约请求,当然这是最好的情况下
         * ,不好的情况下服务续约请求可能达不到20次,这个时候它就有一个最小阈值,也就是85% ,这里就是需要最少有15 次服务续约,当低于这个阈值的时候,就会自动开启自我保护机制,这次服务剔除就不会再进行
         */
        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<>();
        // 遍历注册表,遍历每一个实例信息,调用每个实例租约的lease.isExpired()方法判断有没有过期,additionalLeaseMs是上面计算的补偿时间
        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()) {
                    //当前时间 距离服务上次续约时间超过 90+90 s,超过180s认为过期了。
                    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();
        // 85%
        int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
        // 计算剔除范围,就是不超过注册表所有实例的15%
        int evictionLimit = registrySize - registrySizeThreshold;
        // 剔除数量不超过注册表实例的15%
        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);
            }
        }
    }
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值