美文网首页
Spring Cloud——Eureka自我保护机制

Spring Cloud——Eureka自我保护机制

作者: 小波同学 | 来源:发表于2021-09-10 02:01 被阅读0次

    前言

    以下图片来自Netflix官方,图中显示Eureka Client会发起Renew向注册中心做周期性续约,这样其他Eureka client通过Get Registry请求就能获取到新注册应用的相关信息:

    1、什么是自我保护机制

    默认情况下,如果Eureka Server在一定时间内(默认 90 秒,其实不止 90 秒)没有接收到某个微服务实例的心跳,Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。

    官方对于自我保护机制的定义:
    自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使 Eureka 集群更加的健壮、稳定的运行。

    自我保护机制的工作机制是:如果在15分钟内超过 85% 的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server 自动进入自我保护机制,此时会出现以下几种情况:

    • 1、Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。
    • 2、Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用。
    • 3、当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。

    因此Eureka Server可以很好的应对因网络故障导致部分节点失联的情况,而不会像 ZK 那样如果有一半不可用的情况会导致整个集群不可用而变成瘫痪。

    Eureka Server 自我保护机制,可以通过通过配置 eureka.server.enable-self-preservation 来 true 打开/ false 禁用 自我保护机制,默认打开状态,建议生产环境打开此配置。

    2、Eureka Server 自我保护机制

    Eureka Server 服务过期 当中,在进行服务过期的时候,首先会判断 Eureka Server 是否开启了自我保护机制。

    AbstractInstanceRegistry#evict(long)

    public abstract class AbstractInstanceRegistry implements InstanceRegistry {
    
        public void evict(long additionalLeaseMs) {
            logger.debug("Running the evict task");
    
            if (!isLeaseExpirationEnabled()) {
                logger.debug("DS: lease expiration is currently disabled.");
                return;
            }
            
            //省略剩余代码。。。。。。
        }
    }
    

    如果开启了自我保护机制也就是 isLeaseExpirationEnabled() 方法返回了 false,就直接返回,不进行服务下线。

    PeerAwareInstanceRegistryImpl#isLeaseExpirationEnabled

    public abstract class AbstractInstanceRegistry implements InstanceRegistry {
    
        protected volatile int numberOfRenewsPerMinThreshold;
        
        @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.enableSelfPreservation 为 true 开启反之不开启。如果没有开启直接返回 true,可以进行服务过期处理。

    • 然后判断每分钟期望的续约数(numberOfRenewsPerMinThreshold) 大于 0 并且实际每分钟的续约数(getNumOfRenewsInLastMin()) 大于每分钟期望的续约数(numberOfRenewsPerMinThreshold)

    3、每分钟应用的续约数

    在 Eureka Server 启动的时候,在注册服务(AbstractInstanceRegistry)中会启动一个定时任务 MeasuredRate 来计算每分钟应用续约的个数。时序图如下:


    MeasuredRate,它是一个统计定时任务,在 AbstractInstanceRegistry 的构建器创建 MeasuredRate 对象的时候传入 1000 * 60 * 1,然后在这里调用它的 start 方法里面有一个定时任务,每隔 60 秒也就是每隔 1 分钟执行一次。这个定时任务里面有 2 个 AtomicLong 类型的参数。一个是 AtomicLong currentBucket每进行一次续约的时候就会调用它 + 1,另一个是 AtomicLong lastBucket。

    当 MeasuredRate 任务每分钟进行执行的时候就会把 AtomicLong currentBucket 里面的值设置到 AtomicLong lastBucket当中去,然后把 AtomicLong currentBucket值清空再次计算。然后通过获取 AtomicLong lastBucket 的值就能够得到最近一分钟续约的次数。

    这个设计还是蛮精巧的。

    4、每分钟期望的续约数

    每分钟期望的续约数是 AbstractInstanceRegistry#numberOfRenewsPerMinThreshold ,这个值是动态变化的。它提供了AbstractInstanceRegistry#updateRenewsPerMinThreshold 来动态的更新这个值。

    AbstractInstanceRegistry#updateRenewsPerMinThreshold

    public abstract class AbstractInstanceRegistry implements InstanceRegistry {
    
        protected volatile int numberOfRenewsPerMinThreshold;
        
        protected volatile int expectedNumberOfClientsSendingRenews;
        
        protected void updateRenewsPerMinThreshold() {
            this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
                    * (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
                    * serverConfig.getRenewalPercentThreshold());
        }
    }
    

    每分钟期望的续约数是根据 expectedNumberOfClientsSendingRenews (期望 Eureka Client 发送的续约数,这个值会根据服务的动作进行更新:服务注册 + 1与服务下线 - 1) 来进行判断的。上面的公式如下:

    每分钟期望的续约数 = 期望Eureka Client发送的续约数 * (60 秒 / 预计客户端间隔秒数续约[默认 30 秒]) * 0.85
    
    比如:现在注册中心有 20 个服务
    那么:每分钟期望的续约数 = 20 * (60 / 30) * 0.85 = 17
    

    AbstractInstanceRegistry#register

    public abstract class AbstractInstanceRegistry implements InstanceRegistry {
    
        public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
            
            //省略其余代码。。。。。。
     
            // 租约不存在,因此是新的注册
            synchronized (lock) {
                if (this.expectedNumberOfClientsSendingRenews > 0) {
                    // Since the client wants to register it, increase the number of clients sending renews
                    this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
                    updateRenewsPerMinThreshold();
                }
            }
            
            //省略其余代码。。。。。。
        }
    }
    

    服务注册, expectedNumberOfClientsSendingRenews(期望Eureka Client发送的续约数) + 1,并且更新每分钟期望的续约数。

    AbstractInstanceRegistry#internalCancel

    public abstract class AbstractInstanceRegistry implements InstanceRegistry {
    
        protected boolean internalCancel(String appName, String id, boolean isReplication) {
            
            //省略上面代码。。。。。。
    
            synchronized (lock) {
                if (this.expectedNumberOfClientsSendingRenews > 0) {
                    // Since the client wants to cancel it, reduce the number of clients to send renews.
                    this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews - 1;
                    updateRenewsPerMinThreshold();
                }
            }
    
            return true;
        }
    }
    

    服务下线, expectedNumberOfClientsSendingRenews(期望Eureka Client发送的续约数) - 1,并且更新每分钟期望的续约数。

    参考:
    https://blog.csdn.net/u012410733/article/details/112303048

    相关文章

      网友评论

          本文标题:Spring Cloud——Eureka自我保护机制

          本文链接:https://www.haomeiwen.com/subject/gshgwltx.html