Eureka的自我保护机制大家应该都知道了,当Eureka发现大量的服务实例都故障了,此时是不会下线这些故障服务实例的。Eureka此时会认为是自己的网络故障了,导致无法接收到故障服务实例的心跳信息
然后Eureka就会进入一个自我保护机制,不会再下线任何的服务实例了
如果你在Eureka的控制台看到这段提示,说明就已经触发了自我保护机制
首先来看一下是否触发自我保护机制的源码
@Override
public boolean isLeaseExpirationEnabled() {
// 这个是判断配置文件中的enableSelfPreservation,默认为true
// 如果为false的话,则说明不开启自我保护机制,过期的服务实例都会被摘除
if (!isSelfPreservationModeEnabled()) {
// The self preservation mode is disabled, hence allowing the instances to expire.
return true;
}
// getNumOfRenewsInLastMin()方法计算出了上一分钟服务实例一共发送过来多少次心跳
// numberOfRenewsPerMinThreshold则是期望的一分钟内最少要有多少次心跳
return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;
}
通过上面的注释我们可以发现,要触发自我保护机制,首先配置文件得开启自我保护机制
然后如果上一分钟收到的心跳数大于期望的一分钟内最少要有多少次心跳的话,就说明服务实例数量还是正常的,没有大量的服务实例同时故障,反之则说明大量的服务实例故障,导致上一分钟收到的心跳数还没有达到期望的最少心跳数,此时触发自我保护机制
那么问题来了,getNumOfRenewsInLastMin()方法的上一分钟服务心跳次数和numberOfRenewsPerMinThreshold这个期望的一分钟最少心跳数是怎么算出来的呢?
先来看getNumOfRenewsInLastMin()方法
@Override
public long getNumOfRenewsInLastMin() {
// renewsLastMin这个变量在发送心跳的时候会调用renewsLastMin.increment()不断自增
return renewsLastMin.getCount();
}
我们来找找renewsLastMin这个变量的具体实现
// 我们可以发现renewsLastMin的类型是一个MeasuredRate
private final MeasuredRate renewsLastMin;
// 来看看MeasuredRate是如何实现统计的
public class MeasuredRate {
private static final Logger logger = LoggerFactory.getLogger(MeasuredRate.class);
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;
/**
* @param sampleInterval in milliseconds
*/
// sampleInterval默认是一分钟
public MeasuredRate(long sampleInterval) {
this.sampleInterval = sampleInterval;
this.timer = new Timer("Eureka-MeasureRateTimer", true);
this.isActive = false;
}
public synchronized void start() {
if (!isActive) {
// 启动一个定时任务,每分钟运行
timer.schedule(new TimerTask() {
@Override
public void run() {
try {
// Zero out the current bucket.
// 这里包含两步,将lastBucket设置为currentBucket的值,代表了上一分钟的心跳总数
// 将currentBucket清零,从0开始继续统计下一分钟的心跳数
lastBucket.set(currentBucket.getAndSet(0));
} catch (Throwable e) {
logger.error("Cannot reset the Measured Rate", e);
}
}
}, sampleInterval, sampleInterval);
isActive = true;
}
}
public synchronized void stop() {
if (isActive) {
timer.cancel();
isActive = false;
}
}
/**
* Returns the count in the last sample interval.
*/
// 这个方法可以看到,当取得上一分钟的心跳次数时使用的是lastBucket
public long getCount() {
return lastBucket.get();
}
/**
* Increments the count in the current sample interval.
*/
// 这个方法可以看到,心跳时增加心跳次数使用的是currentBucket
public void increment() {
currentBucket.incrementAndGet();
}
}
通过上面的代码,我们可以发现统计上一分钟的心跳次数其实就是一个每分钟运行的定时任务,通过两个变量,其中lastBucket存储心跳次数,currentBucket增加心跳次数来实现
接着看每分钟最少心跳次数的源码
protected void updateRenewsPerMinThreshold() {
// expectedNumberOfClientsSendingRenews这个变量代表的是服务实例的数量
// serverConfig就是获取配置文件中配置,expectedClientRenewalIntervalSeconds默认30;renewalPercentThreshold默认0.85
// (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())的意思就是每分钟每个服务实例所发送的心跳次数
this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
* (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
* serverConfig.getRenewalPercentThreshold());
}
通过上面的分析,可以得到一个公式numberOfRenewsPerMinThreshold = 服务实例数量 * 每个服务实例每分钟发送的次数 * 更新比例,默认情况下就是 服务实例数量 * 2 * 0.85
通过源码的分析,Eureka的自我保护机制,其实很简单,就是如果每一分钟接收到的心跳次数比正常的心跳次数的85%(默认情况下)还少的话,那么就认为是Eureka本身出现了故障,无法接收到服务实例的心跳,从而不再下线其他的服务实例