• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Java的Eureka系列:深度的解析Eureka的自我保护机制

武飞扬头像
juejin
帮助110

前言

Java的Eureka系列:深度的解析Eureka的自我保护机制

0. 环境

  • eureka版本:1.10.11
  • Spring Cloud : 2020.0.2
  • Spring Boot :2.4.4

1.自我保护机制介绍

image.png

想必用过eureka 的同学都在eureka web控制台见过这么一行大红字,我现在还能想起来第一次用eureka 出现这个的心里的那种感觉,什么东西,这么显眼,然后就很懵逼,立马搜了一下这个是什么鬼,就搜到说eureka自我保护机制触发了,说是 “eureka 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,eureka 会将这些实例保护起来,让这些实例不会过期”(需要注意这句话的前半部分,我们这篇文章将用源码推翻它),其实就是某段时间内收到心跳数量低于期望心跳数量85%,就会触发这个自我保护机制,这个时候,服务实例将不会被过期

2.实现原理

  • 一个核心原理:实际收到的心跳 小于 我心里能接受的最小心跳数 ,这个时候就会触发eureka 自我保护机制

  • 两个概念(都很好理解):

    • 一个是期望心跳数,期望心跳数与服务实例多少是有关系的,比如说我注册表中有10个实例,然后每30s发送一次心跳(也就是续约),那么我这个每分钟期望心跳数就是 10 * 2 ,也就是我期望收到 20个心跳。
    • 还有一个是我心里能接受的最小心跳数,网络环境这么复杂,你总不能让这些实例的心跳一个都不少吧,所以这里就出现了一个心里能接受的最小心跳数(心跳数阈值),它的计算方式就是 期望心跳数 * 0.85 (这个0.85 是默认的,是不是与上面那个85% 就对起来了)

有了 期望心跳数最小心跳数阈值,我们现在还差的是啥?那就是统计收到的心跳数:

比如说我专门有个计数器,然后以一个时间单位为一个窗口,就是统计每分钟收到的心跳次数(续约次数),其实光有收集当前分钟心跳数的计数器还不够,总不能拿当前分钟的心跳数与心里能接受的最小心跳数 做比较吧,那分钟开始的时候,肯定会触发这个自我保护机制(毕竟分钟开始的时候,没收到多少心跳)

这个时候一个计数器就不够了,还得需要一个,一个用于统计当前分钟收到的心跳数量,一个用于存储上一分钟收到的心跳数,然后拿上一分钟收到的心跳数 与 心里能接受的最小心跳数 做比较,然后小于心里能接受的最小心跳数,就触发自我保护机制

有了2个心跳计数器,还不够,还缺一个定时器,然后每分钟执行一次将 当前分钟收集到的心跳数 给 存储上一分钟这个计数器上,然后将当前分钟计数器清0,表示开启新一轮的计数。

到这里,其实大体上就已经讲清楚了,但是还有几个点:

  • 最开始的时候期望心跳数是怎么出来的?
  • 服务注册,服务下线 这个期望心跳数是怎样变化的?

现在解答一下:

  • 最开始的时候,eureka server 启动会去其他server节点上拉取注册表,如果拉到注册表的话,遍历注册表里面的实例信息,然后挨个 按照注册的方式 注册到本机的注册表上,它会有一个返回一个注册多少实例count ,接着就是按照这个count *2 初始化的。
  • 服务注册的时候,期望心跳数 2 ,我多一个实例,一个实例每分钟默认发送2个心跳,这个时候就会 2 。服务下线的时候 期望心跳数 -2,然后心里能接受的最小心跳数 也会重新算一遍。

好了,以上就是eureka 自我保护机制的实现原理了,只不过我用自己的话将代码翻译了一遍,如果还是不能明白,可以看一下第4节的那张图。接下来我们就要看一下源码了

3.源码解析

3.1期望心跳数 与 我心里能接受的最小心跳数

先看一下关于 期望心跳数 与 我心里能接受的最小心跳数 变量的定义 在 eureka-core 项目的注册表抽象类AbstractInstanceRegistry 中定义了这两个变量:

// 服务端统计最近一分钟预期收到客户端实例心跳续租的请求数 我心里能接受的最小心跳数
protected volatile int numberOfRenewsPerMinThreshold;
// 服务端统计预期收到心跳续租的客户端实例数 期望心跳数
protected volatile int expectedNumberOfClientsSendingRenews;

接着看下初始化,服务注册,服务下线 期望心跳数 与 我心里能接受的最小心跳数 变动。

3.1.1 初始化

eureka server在启动的时候,会进行初始化,它会到其他server 节点上同步一下注册表,然后注册到自己本地的注册表中,这个时候会返回一个同步数量count,后面注册表中的这两个变量会根据这个count计算出来

// com.netflix.eureka.EurekaBootStrap#initEurekaServerContext
int registryCount = registry.syncUp();
registry.openForTraffic(applicationInfoManager, registryCount);

先是同步注册表,返回同步实例的数量。

@Override
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
    // Renewals happen every 30 seconds and for a minute it should be a factor of 2.
    // 预期收到心跳续租的实例数赋值
    this.expectedNumberOfClientsSendingRenews = count;
    // todo 更新预期每分钟收到心跳续租请求数
    updateRenewsPerMinThreshold();
    ...
}

接着就是调用注册表的 openForTraffic 方法,初始化这两个变量,改变applicationInfoManager 为up状态,完成注册表 初始化之后的动作

这里期望心跳数 直接就是同步实例数量*2我心里能接受的最小心跳数 是期望心跳数 * 0.85(默认是0.85)

详见Java的Eureka系列:同步注册表机制

3.1.2 服务注册

在注册表抽象类AbstractInstanceRegistry 中的register方法中

image.png

可以看到 期望心跳数 2 ,然后 我心里能接受的最小心跳数 重新算了一遍。

3.1.3 服务下线

这个是在注册表抽象类AbstractInstanceRegistry 的实现类PeerAwareInstanceRegistryImpl中的cancel 方法下internalCancel()做的处理:

image.png

它这里有个很大的问题就是这个 2 和-2 直接写死了,如果我心跳间隔改变了,这个时候不是30s发一次心跳了,变成了1分钟一次,然后eureka 就会一直处于自我保护机制中

3.2 关于计数器

3.2.1 计数器初始化

eureka server 启动的时候,会执行一堆初始化,这个是在EurekaBootstrap中的,之后就是创建注册表,在注册表抽象类AbstractInstanceRegistry的构造方法中创建了这个计数器。

image.png

然后再初始化最后面调用了一下注册表的openForTraffic 方法,初始化期望心跳数我心里能接受的最小心跳数 这两个变量,改变applicationInfoManager 为up状态,完成注册表 初始化之后的动作,就在注册表 初始化之后的动作里面启动了计数器。

image.png

先来看下 MeasuredRate 这类中关键的几个成员变量:

// 除了这里使用这个计数任务工具类
// 还有 PeerAwareInstanceRegistryImpl.numberOfReplicationsLastMin 字段也使用该工具类
// numberOfReplicationsLastMin:服务端统计最后一分钟同步复制给集群节点的操作数
public class MeasuredRate {
    private static final Logger logger = LoggerFactory.getLogger(MeasuredRate.class);
    // 最近一分钟(上一分钟)的计数
    // getCount() 返回该计数
    private final AtomicLong lastBucket = new AtomicLong(0);
    // 当前一分钟正在统计的计数
    private final AtomicLong currentBucket = new AtomicLong(0);
    // 定时任务执行间隔
    private final long sampleInterval;
    private final Timer timer;

    private volatile boolean isActive;
}

它的start() 方法其实就是启动这个定时任务

image.png

它这里是1分钟执行一次,就是sampleInterval 这个成员决定的,这个成员实在创建这个对象的传过来的,然后就是1分钟。

看下这个画红框的代码,其实就是将 当前分钟收集到的心跳数存储上一分钟这个计数器 lastBucket上,然后将当前分钟计数器 currentBucket清0,表示开启新一轮的计数。

3.2.2 心跳计数

其实每收到一个心跳(续约),都会给currentBucket 这变量自增1,我们看一下代码

public boolean renew(String appName, String id, boolean isReplication) {
    
        // todo renew计数,最近一分钟处理的心跳续租数 1,自我保护机制会用到
        renewsLastMin.increment();
        // todo 续约
        leaseToRenew.renew();
        return true;
    }
}

public void increment() {
    currentBucket.incrementAndGet();
}

3.3 关于自我保护模式的判断

public int isBelowRenewThresold() {
    if ((getNumOfRenewsInLastMin() <= numberOfRenewsPerMinThreshold)
            &&
            ((this.startupTime > 0) && (System.currentTimeMillis() > this.startupTime   (serverConfig.getWaitTimeInMsWhenSyncEmpty())))) {
        return 1;
    } else {
        return 0;
    }
}

public long getNumOfRenewsInLastMin() {
    return renewsLastMin.getCount();
}

上面就是就是自我保护模式的判断了,很清楚。

3.4 eureka web中那行大红字

resource 的header.jsp中有这么几行

image.png

先是调用注册表的isBelowRenewThresold 方法判断是不是触发了自我保护机制:

image.png

要想显示那行触发自我保护机制的大红字,需要 系统启动了5分钟以上,而且触发了自我保护机制,其实还需要isSelfPreservationModeEnabled 这个参数是true,这样子才会显示这个触发自我保护的大红字

3.5 触发自我保护对过期下线的影响

在注册表 postInit方法中,其实还启动一个定时任务,扫描那些过期的实例,然后进行服务下线动作,这里主要是介绍触发自我保护对过期下线的影响,不会深入剖析服务扫描下线这一堆的东西

image.png 这个定时任务是1分钟执行一次的。

image.png 进入这个evict 方法中。

image.png 如果触发了自我保护机制就不往下走了,其实下面就是扫描注册表中所有的实例信息,然后看看过没过期,过期就走服务下线逻辑。我们来看下这个isLeaseExpirationEnabled方法的实现。

image.png 如果没有启用这个自我保护机制的话,直接通过,走下面服务扫描过期的逻辑。如果启用了,还用判断 收到的心跳数大于期望最小心跳数 ,这个时候才能继续往下走,如果是小于的话,就是触发自我保护机制了,就不会再往下走了,也就是不会再扫描下线过期的实例。

4. 流程图

image.png

5. 总结

本篇文章主要是介绍了eureka自我保护机制的实现原理,不管是语言描述,还是代码剖析,都已经很明了了。 我没有找到所谓的 15分钟内心跳失败比例小于85% 就是触发自我保护机制,在源码中,我只分析出 你上一分钟心跳数 低于 期望心跳数的85% 就会触发自我保护机制,我不知道他们所谓的15分钟是哪里的,我只相信源码出真知,然后这里还发现计算期望心跳数 的硬编码问题,如果你心跳间隔改变了,不是30s了,就很容易出现bug。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanehji
系列文章
更多 icon
同类精品
更多 icon
继续加载