Eureka Server源码解析(服务故障下线剔除流程)

本文详细解析了EurekaServer如何通过服务剔除任务定期检查并移除长时间未续约的实例,以及自我保护机制的运作原理。在启动后,EurekaServer每60秒执行一次服务剔除任务,判断服务是否超过180s未续约。自我保护机制在服务续约请求低于预设阈值时开启,防止异常情况下误剔除健康服务。此外,介绍了服务剔除的具体步骤和数量限制。
摘要由CSDN通过智能技术生成

原创不易,转载注明出处

系列文章目录

  1. SpringCloud Eureka Server源码解析(启动流程)
  2. Eureka Server源码解析(服务注册流程)
  3. Eureka Server源码解析(服务续约流程)
  4. Eureka Server源码解析(服务主动下线流程)
  5. 深度解析Eureka的自我保护机制


前言

本文主要是解析下Euraka Server 是怎样剔除那些“失联”服务实例的源码,一个服务注册到Eureka Server 上后,默认会30s 发送一次服务续约请求告诉Eureka Server 自己还活着,如果一个服务突然挂了,并没有主动向Eureka Server 发送服务下线请求,并没有从注册表删除该实例信息,其他服务拉取注册表的时候还能拉取到这个实例信息,并且使用的有问题,对于这种突然“失联”的服务实例,Eureka Server 是怎样做的呢? 服务故障剔除,Eureka Server 启动的时候,会启动一个定时任务,默认是每60s 扫描一次注册表,看看哪些服务实例长时间没有发送续约请求,就会将这些服务从注册表中剔除,默认是90s 没有续约的服务实例(但实际上90+90s ,在那段判断逻辑中,有段注释说该处是bug ,但是他并不改, 这里是我们使用Eureka 作为注册中心的时候需要注意的一个点)
在这里插入图片描述

1.源码解析

1.1 创建故障剔除任务

在Eureka Server 初始化启动完成后,会创建一个服务剔除定时任务,默认每个60s扫描一次注册表,我们来看看定时任务创建流程代码。
在EurekaBootStrap 的initEurekaServerContext 方法最后面有 registry.openForTraffic(applicationInfoManager, registryCount); 这行代码
它会改变Eureka Server 服务状态为UP,在它UP之后有一行,调用父类postInit方法。

applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
super.postInit();

也就是注册表父类AbstractInstanceRegistry 的postInit方法

protected void postInit() {
     renewsLastMin.start();
     if (evictionTaskRef.get() != null) {
         evictionTaskRef.get().cancel();
     }
     evictionTaskRef.set(new EvictionTask());
     evictionTimer.schedule(evictionTaskRef.get(),
             serverConfig.getEvictionIntervalTimerInMs(),
             serverConfig.getEvictionIntervalTimerInMs());
 }

这个方法干了2件事情,1是启动了一个定时任务,用作每分钟的服务续约renew 统计,2是启动一个服务剔除定时任务,默认是60s执行一次。

public long getEvictionIntervalTimerInMs() {
    return configInstance.getLongProperty(
            namespace + "evictionIntervalTimerInMs", (60 * 1000)).get();
}

可以看到,如果你不指定的话,默认是60执行一次。

1.2 服务剔除流程

在1.1小节中,我们看了服务剔除定时任务的创建,执行的任务是EvictionTask ,接下来我们看下它是怎样服务故障剔除的。

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

getCompensationTimeMs 这个方法防止服务器时钟发生问题,做出的时间补偿,这里我们不需要过多的关注。
调用了evict 方法进行服务剔除,方法比较长,我们一部分一部分的看下

1.2.1 自我保护机制

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

是否启用服务故障剔除。

@Override
public boolean isLeaseExpirationEnabled() {
    if (!isSelfPreservationModeEnabled()) {
        // The self preservation mode is disabled, hence allowing the instances to expire.
        return true;
    }
    return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;
}

先是判断配置开没开启故障剔除(默认是开启的),然后再判断是否触发了自我保护机制。
这里我解释下自我保护机制是怎样触发的, 比如说每一个新的实例注册到Eureka Server ,Eureka 都会记录下来当前注册了多少个实例,再就是默认客户端每30s进行服务续约一次,一分钟1个实例就是2次服务续约,它都会记录下来,按照这个规则,如果我有10个实例注册到了Eureka Server 上,那么按照正常流程下,每分钟就会有20个服务续约请求,当然这是最好的情况下,不好的情况下服务续约请求可能达不到20次,这个时候它就有一个最小阈值,也就是85% ,这里就是需要最少有15 次服务续约,当低于这个阈值的时候,就会自动开启自我保护机制,这次服务剔除就不会再进行。关于Eureka Server自我保护机制 详细计算规则请查看我的另一篇文章《深度解析Eureka的自我保护机制

1.2.2 服务剔除

再接着往下

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

可以看到这里直接遍历注册表,遍历每一个实例信息,调用每个实例租约的lease.isExpired(additionalLeaseMs) 方法判断有没有过期,其中additionalLeaseMs是上面计算的补偿时间。
在这里插入图片描述
规则就是:当前时间 大于 上次续约计算出来的时间 + duration(默认是90s)+ 补偿时间。

上次续约计算出来的时间 :

public void renew() {
   lastUpdateTimestamp = System.currentTimeMillis() + duration;
}

说白了就是 当前时间 距离服务上次续约时间超过 90+90 s,超过180s认为过期了。
过期的实例信息会被塞到一个集合中。

// 注册表中所有实例信息
int registrySize = (int) getLocalRegistrySize();
// 85% 
int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
// 计算剔除范围, 就是不超过注册表所有实例的15% 
int evictionLimit = registrySize - registrySizeThreshold;
// 剔除数量不超过注册表实例的15%
int toEvict = Math.min(expiredLeases.size(), evictionLimit);

这段代码就是计算出来要剔除服务实例的数量,默认是不超过注册表所有实例数量的15% ,比如说我注册表中有100个实例,但是上面发现有20个实例过期了,但是根据这个规则,这次服务剔除,只能剔除15个。

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

上面这段代码就是真正的服务剔除了,根据上面的规则,只能剔除15个服务实例,所以就遍历15次。服务剔除走的服务下线逻辑,并且不进行集群节点间的同步。

2.流程图

在这里插入图片描述

总结

Eureka Server 初始化完成后,会启动一个定时任务,定时扫描注册表中(默认是60s 扫描一次),专门清除那些长时间没有续约(发送心跳)的客户端实例,将实例信息从注册表中剔除(默认是2倍的duration ,一个duration默认是90s,180s没有发送心跳的服务实例会被从注册表中剔除,),但是需要注意的是,自我保护机制触发后不会剔除这些服务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

$码出未来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值