概述
背景:项目进入编码阶段,首先要实现一个注册中心给其他服务用,过程中开启了两个注册中心实例: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 分钟收到客户端实例续约的总数。
这两个参数是怎么计算的呢?
看一下源码:
-
protected void updateRenewsPerMinThreshold() {
-
this.numberOfRenewsPerMinThreshold = (int)((double)this.expectedNumberOfClientsSendingRenews * (60.0D / (double)this.serverConfig.getExpectedClientRenewalIntervalSeconds()) * this.serverConfig.getRenewalPercentThreshold());
-
}
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开启自我保护机制,(而不是像第一个例子那样由于自身状态莫名触发自我保护机制)。
最后
以上就是合适钢笔为你收集整理的Spring Eureka自我保护机制的全部内容,希望文章能够帮你解决Spring Eureka自我保护机制所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复