Eureka 源码解析 —— 应用实例注册发现(四)之自我保护机制

来源:互联网 发布:钱规则网络剧 编辑:程序博客网 时间:2024/05/19 21:02

摘要: 原创出处 http://www.iocoder.cn/Eureka/instance-registry-self-preservation/ 「芋道源码」欢迎转载,保留摘要,谢谢!

本文主要基于 Eureka 1.8.X 版本

  • 1. 概述
  • 2. 定义
  • 3. 实现
    • 3.1 触发条件
    • 3.2 计算公式
    • 3.3 计算时机



  • 1. 概述

    本文主要分享 自我保护机制,为应用实例过期下线做铺垫。

    推荐 Spring Cloud 书籍

    • 请支持正版。下载盗版,等于主动编写低级 BUG
    • 程序猿DD —— 《Spring Cloud微服务实战》
    • 周立 —— 《Spring Cloud与Docker微服务架构实战》

    2. 定义

    自我保护机制定义如下:

    FROM 周立 —— 《理解Eureka的自我保护模式》
    当Eureka Server节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,Eureka Server就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。

    为什么使用自动保护机制 ?你也可以从周立兄的这篇文章得到答案,这里笔者就不一本正经的胡说八道了。

    3. 实现

    首先,我们来看下在自动保护机制里扮演重要角色的两个变量:


    // AbstractInstanceRegistry.java
    /
    * 期望最小每分钟续租次数
    /
    protected volatile int numberOfRenewsPerMinThreshold;
    /
    期望最大每分钟续租次数
    */
    protected volatile int expectedNumberOfRenewsPerMin;
    • expectedNumberOfRenewsPerMin ,期望最大每分钟续租次数。
    • numberOfRenewsPerMinThreshold ,期望最小每分钟续租次数。

    3.1 触发条件

    当每分钟心跳次数( renewsLastMin ) 小于 numberOfRenewsPerMinThreshold 时,并且开启自动保护模式开关( eureka.enableSelfPreservation = true ) 时,触发自动保护机制,不再自动过期租约,实现代码如下:


    // AbstractInstanceRegistry.java
    public void evict(long additionalLeaseMs) {
    if (!isLeaseExpirationEnabled()) {
    logger.debug("DS: lease expiration is currently disabled.");
    return;
    }
    // ... 省略过期租约逻辑
    }
    // PeerAwareInstanceRegistryImpl.java
    @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;
    }

    3.2 计算公式

    计算公式如下:

    • expectedNumberOfRenewsPerMin = 当前注册的应用实例数 x 2
    • numberOfRenewsPerMinThreshold = expectedNumberOfRenewsPerMin * 续租百分比( eureka.renewalPercentThreshold )

    为什么乘以 2

    默认情况下,注册的应用实例每半分钟续租一次,那么一分钟心跳两次,因此 x 2 。

    这块会有一些硬编码的情况,因此不太建议修改应用实例的续租频率

    为什么乘以续租百分比

    低于这个百分比,意味着开启自我保护机制。

    默认情况下,eureka.renewalPercentThreshold = 0.85

    如果你真的调整了续租频率,可以等比去续租百分比,以保证合适的触发自我保护机制的阀值。另外,你需要注意,续租频率是 Client 级别,续租百分比是 Server 级别。

    3.3 计算时机

    目前有个地方会计算 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin,我们逐小节来看。


    3.3.1 Eureka-Server 初始化

    Eureka-Server 在启动时,从 Eureka-Server 集群获取注册信息,并首次初始化 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin 。实现代码如下:


    // EurekaBootStrap.java
    protected void initEurekaServerContext() throws Exception {
    // ... 省略其它代码
    // 【2.2.10】从其他 Eureka-Server 拉取注册信息
    // Copy registry from neighboring eureka node
    int registryCount = registry.syncUp();
    registry.openForTraffic(applicationInfoManager, registryCount);
    // ... 省略其它代码
    }
    // PeerAwareInstanceRegistryImpl.java
    @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.expectedNumberOfRenewsPerMin = count * 2;
    this.numberOfRenewsPerMinThreshold =
    (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
    // ... 省略其它代码
    }

    3.3.2 定时重置

    Eureka-Server 定时重新计算 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin 。实现代码如下:


    // PeerAwareInstanceRegistryImpl.java
    private void scheduleRenewalThresholdUpdateTask() {
    timer.schedule(new TimerTask() {
    @Override
    public void run() {
    updateRenewalThreshold();
    }
    }, serverConfig.getRenewalThresholdUpdateIntervalMs(),
    serverConfig.getRenewalThresholdUpdateIntervalMs());
    }
    // AbstractInstanceRegistry.java
    /*
    自我保护机锁
    当计算如下参数时使用:
    * 1. {@link #numberOfRenewsPerMinThreshold}
    * 2. {@link #expectedNumberOfRenewsPerMin}
    /
    protected final Object lock = new Object();
    private void updateRenewalThreshold() {
    try {
    // 计算 应用实例数
    Applications apps = eurekaClient.getApplications();
    int count = 0;
    for (Application app : apps.getRegisteredApplications()) {
    for (InstanceInfo instance : app.getInstances()) {
    if (this.isRegisterable(instance)) {
    ++count;
    }
    }
    }
    // 计算 expectedNumberOfRenewsPerMin 、 numberOfRenewsPerMinThreshold 参数
    synchronized (lock) {
    // Update threshold only if the threshold is greater than the
    // current expected threshold of if the self preservation is disabled.
    if ((count 2) > (serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold)
    || (!this.isSelfPreservationModeEnabled())) {
    this.expectedNumberOfRenewsPerMin = count * 2;
    this.numberOfRenewsPerMinThreshold = (int) ((count * 2) * serverConfig.getRenewalPercentThreshold());
    }
    }
    logger.info("Current renewal threshold is : {}", numberOfRenewsPerMinThreshold);
    } catch (Throwable e) {
    logger.error("Cannot update renewal threshold", e);
    }
    }
    • 配置 eureka.renewalThresholdUpdateIntervalMs 参数,定时重新计算。默认,15 分钟。
    • 代码块 !this.isSelfPreservationModeEnabled() :当未开启自我保护机制时,每次都进行重新计算。事实上,这两个参数不仅仅自我保护机制会使用到,配合 Netflix Servo 实现监控信息采集 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin
    • 代码块 (count * 2) > (serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold) :当开启自我保护机制时,应用实例每分钟最大心跳数( count * 2 ) 小于期望最小每分钟续租次数( serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold ),不重新计算。如果重新计算,自动保护机制会每次定时执行后失效

    3.3.3 应用实例注册

    应用实例注册时,增加 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin 。实现代码如下:


    //
    public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
    // ... 省略无关代码
    // The lease does not exist and hence it is a new registration
    // 【自我保护机制】增加 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin
    synchronized (lock) {
    if (this.expectedNumberOfRenewsPerMin > 0) {
    // Since the client wants to cancel it, reduce the threshold
    // (1
    // for 30 seconds, 2 for a minute)
    this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2;
    this.numberOfRenewsPerMinThreshold =
    (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
    }
    }
    // ... 省略无关代码
    }

    3.3.4 应用实例下线

    应用实例下线时,减少 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin 。实现代码如下:


    // PeerAwareInstanceRegistryImpl.java
    @Override
    public boolean cancel(final String appName, final String id,
    final boolean isReplication) {
    // ... 省略无关代码
    synchronized (lock) {
    if (this.expectedNumberOfRenewsPerMin > 0) {
    // Since the client wants to cancel it, reduce the threshold (1 for 30 seconds, 2 for a minute)
    this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin - 2;
    this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
    }
    }
    // ... 省略无关代码
    }
阅读全文
0 0
原创粉丝点击