Spring Eureka自我保护机制

springcloud 专栏收录该内容
23 篇文章 0 订阅

背景:项目进入编码阶段,首先要实现一个注册中心给其他服务用,过程中开启了两个注册中心实例:peer1和peer2,它们互相注册为对方的服务。

疑惑:查看Dashboard的时候,除了查看实例的那一部分比较明白,偶尔会出现这样的红色警告:

EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

其实在学习的过程中也知道这是触发了Eureka的保护机制,Eureka有一个自我保护机制,自我保护机制大概是这样:

服务注册到Eureka Server之后,会维护一个心跳连接,那么Eureka Server在运行期间会统计心跳失败的比例在15分钟内是否低于85%,如果出现低于的情况,Eureka Server会将当前的实例注册信息保护起来,不会让它们立刻过期。但是在保护期内如果实例出现问题,那么客户端很容易拿到已经不存在的实例,所以需要有容错机制(这是后话).

保护机制在生产环境中,通常是为了防止因网络原因而导致原本没有问题的服务被清除。

一旦开启了保护机制,则服务注册中心维护的服务实例就不是那么准确了。

 这里多说一点,那Eureka Server故障了怎么判断

Eureka Server认为,当少量服务超时是认为是服务故障,若大量服务都超时了,则认为自己发生了故障。

自我保护模式开启的条件是:1 分钟后,若 Renews (last min) < Renews threshold,那么开启自我保护机制 

这两个参数是在DashBoard中可以看到的。

Renews threshold表示:Eureka Server 期望每分钟收到客户端实例续约的阈值。

Renews(last min)表示:Eureka Server 最后 1 分钟收到客户端实例续约的总数

这两个参数是怎么计算的呢?

看一下源码:

 
  1. protected void updateRenewsPerMinThreshold() {

  2. this.numberOfRenewsPerMinThreshold = (int)((double)this.expectedNumberOfClientsSendingRenews * (60.0D / (double)this.serverConfig.getExpectedClientRenewalIntervalSeconds()) * this.serverConfig.getRenewalPercentThreshold());

  3. }

numberOfRenewsPerMinThreshold就是Dashboard中的Renews threshold

expectedNumberOfClientsSendingRenews:期望收到客户端续约的总数 (服务的总数)

getExpectedClientRenewalIntervalSeconds():获取客户端续约间隔(秒为单位)的方法。(默认30s)

getRenewalPercentThreshold():获取自我保护续约百分比阈值因子。(默认85%)

那么: 

Renews threshold = 服务实例总数 *(60/续约间隔)*自我保护续约百分比阈值因子。

Renews(last min) = 服务实例总数 * (60/续约间隔)

了解了这几个参数以后,我们看一下自我保护开启的条件

 之前说是 Renews (last min) < Renews threshold

看下源码:

public boolean isLeaseExpirationEnabled() {

if (!this.isSelfPreservationModeEnabled()) {

return true;

} else {

return this.numberOfRenewsPerMinThreshold > 0 && this.getNumOfRenewsInLastMin() > (long)this.numberOfRenewsPerMinThreshold;

}

}

 这个方法是在Eureka Server清理服务时调用,判断是否需要清理

逻辑:如果自我保护模式没开启,那就可以清理。如果自我保护模式开启了,且当续约阈值>0,上一分钟的续约数>阈值,那么可以清理。言外之意就是,当上一分钟续约数<阈值,那么就不清理(保护了)

那么expectedNumberOfClientsSendingRenews是怎么算的?其实就是服务实例的总数。

---------------------------分割线-------------------------------------------------------

有了理论知识,那么我们来具体的看一下不同情况下自我保护的具体例子

第一种:

 这里我开启了一个Eureka Server并关闭了它的自动注册功能,然后让一个Book-Service注册进来。

Renews threshold = (1+1) * (60/30) * 0.85 = 3 ,这里1+1表示(1个eureka server + 1个book-service服务)

Renews(last min) = 2 是book-service发送来的续约请求,1分钟2次。

所以说Renews(last min) < Renews threshold,此时Lease expiration enabled是false表示不会清除实例(也就是开启了自我保护) 

这里有一个疑问,当注册中心不自动注册的时候(上一个例子)计算实例时也要把它算上吗?

(是的,实际上它就是一个服务实例呀,只不过它不注册自己)

第2种:

我开启了两个Eureka Server实例并互相注册,此时

Renews threshold = (1+1)*(60/30)*0.85 = 3 ,这里的1+1是两个实例

Renews(last min)= 4  一个实例1分钟发送两次续约请求,所以这里是2*2=4

Renews (last min)> Renews threshold 所以这时候可以清理实例,因为都正常。(Eureka默认状态下是开启自我保护开关的)

 

看下一个例子:

 我把book-service注册到peer1中,查看peer2的dashboard时发现是上面这个情况,又有两个疑问:

1.为什么peer2也会有book-service呢?

因为两个注册中心是具有服务同步功能的,当book-service注册到peer1上时,peer1会把请求也发送到peer2中,从而实现服务同步,通过服务同步,book-service的信息就可以通过这两个注册中心的任意一个来获取。

2.为什么这里触发了自我保护机制?

因为我刚把book-service注册进来,这里Renews threshold就进行了更新(待会讲这个自我保护阈值的情况)

Renews threshold = (1+2) * (60/30) * 0.85 = 5

Renews(last min)= 4 这里的值还是两个注册中心续约的情况 2*2=4,因为这时候还没有收到book-service的续约请求,所以触发了自我保护机制。

再1分钟更新DashBoard,就可以发现现在的Renews (last min) = 6了

----------------------------------------------------------分割线---------------------------------------------

这里再看一下什么时候会触发Renews Threshold 的更新

(Renews Threshold 就是源码里的 numberOfRenewsPerMinThreshold

1.新服务注册进来时(register)

(这里省去了register()中其他逻辑)

synchronized(this.lock) {

if (this.expectedNumberOfClientsSendingRenews > 0) {

++this.expectedNumberOfClientsSendingRenews;

this.updateRenewsPerMinThreshold();

}

}

当有新服务(实例)注册进来时,expectedNumberOfClientsSendingRenews会增加,然后触发updateRenewsPerMinThreshold()更新threshold。

2.注销服务时(cancel)

public boolean cancel(String appName, String id, boolean isReplication) {

if (super.cancel(appName, id, isReplication)) {

this.replicateToPeers(PeerAwareInstanceRegistryImpl.Action.Cancel, appName, id, (InstanceInfo)null, (InstanceStatus)null, isReplication);

synchronized(this.lock) {

if (this.expectedNumberOfClientsSendingRenews > 0) {

--this.expectedNumberOfClientsSendingRenews;

this.updateRenewsPerMinThreshold();

}

return true;

}

} else {

return false;

}

}

注销时,expectedNumberOfClientsSendingRenews会减少,然后触发updateRenewsPerMinThreshold()更新threshold。

3.Task定时任务(默认15分钟)


    private void scheduleRenewalThresholdUpdateTask() {

    this.timer.schedule(new TimerTask() {

    public void run() {

    PeerAwareInstanceRegistryImpl.this.updateRenewalThreshold();

    }

    }, (long)this.serverConfig.getRenewalThresholdUpdateIntervalMs(), (long)this.serverConfig.getRenewalThresholdUpdateIntervalMs());

    }

-------------------------------------------------分割线------------------------------------------------

写在后面:

有时候自我保护模式会导致在开发的过程中维护的实例不那么准确,在网上查到有以下方式:

  • 关闭自我保护模式(eureka.server.enable-self-preservation设为false),不推荐
  • 降低renewalPercentThreshold的比例(eureka.server.renewal-percent-threshold设置为0.5以下,比如0.49),不推荐
  • 部署多个 Eureka Server 并开启其客户端行为(eureka.client.register-with-eureka不要设为false,默认为true),推荐

Eureka 的自我保护模式是有意义的,该模式被激活后,它不会从注册列表中剔除因长时间没收到心跳导致租期过期的服务,而是等待修复,直到心跳恢复正常之后,它自动退出自我保护模式。这种模式旨在避免因网络分区故障导致服务不可用的问题。例如,两个客户端实例 C1 和 C2 的连通性是良好的,但是由于网络故障,C2 未能及时向 Eureka 发送心跳续约,这时候 Eureka 不能简单的将 C2 从注册表中剔除。因为如果剔除了,C1 就无法从 Eureka 服务器中获取 C2 注册的服务,但是这时候 C2 服务是可用的。

--------------以上来自:https://www.cnblogs.com/xishuai/archive/2018/04/20/spring-cloud-eureka-safe.html

所以我们开发的时候,可以部署两个eureka server互相注册,既实现了高可用的注册中心,也保证了Eureka Serve开启自我保护机制,(而不是像第一个例子那样由于自身状态莫名触发自我保护机制)。

  • 1
    点赞
  • 2
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值