Eureka的
自我保护机制
是为了防止误杀服务。当注册中心发生故障,服务不能够正常的续约,但是服务运行正常,默认情况下,Eureka会将超过90s未续约的服务进行移除。这样做明显不合理,所以Eureka提供了一个自我保护机制。
进行服务注册时,Eureka Client
会向Eureka Server
发送第一次心跳,会将服务的实例信息注册到注册中心
Eureka Client
每30秒来发送一次心跳来更新实例信息。通知Eureka Server
该实例仍然存在。如果超过90秒没有发送更新,则服务器将从注册信息中将此服务移除。
续约时间可以更改,建议不要更改。
Eureka Client
从Eureka Server
获取注册表信息并在本地缓存
它,之后,客户端使用该信息来查找其他服务。此信息会定期更新,30s更新一次,每次发送心跳的时候,就会与Eureka Server
进行信息同步。
Eureka Client
在关闭时,会发送一个shutdown请求。这将从Eureka Server
的实例注册表中删除实例,从而有效地使实例脱离流量。
Eureka Server
会定时来刷新缓存,来确保注册服务的可用性。Eureka Client
会定期来拉取(Fetch
)注册信息。
这里存在一个问题,如何判断是Eureka Server
故障,还是服务故障,Eureka Server
提供的判断条件是,当出现大量的服务续约超时,那么就会认为自己出现了问题。如果出现少量了服务续约超时,则认为服务故障。
是否开启自我保护机制,实质上就是计算续约的服务,由于续约服务的计算是间歇性的,因此判断的条件就是两次更新的间隔的差值,这里提供了一个阀值numberOfRenewsPerMinThreshold
和上一分钟的续约数进行对比,如果实际的续约数小于了自我保护阀值,则开启自我保护。
自我保护阀值的计算:
com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl
private void scheduleRenewalThresholdUpdateTask() {
timer.schedule(new TimerTask() {
@Override
public void run() {
updateRenewalThreshold();
}
}, serverConfig.getRenewalThresholdUpdateIntervalMs(),
serverConfig.getRenewalThresholdUpdateIntervalMs());
}
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;
}
}
}
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);
}
}
public void register(InstanceInfo registrant, int leaseDuration, 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());
}
}
logger.debug("No previous lease information found; it is new registration");
@Override
public boolean cancel(final String appName, final String id,
final boolean isReplication) {
if (super.cancel(appName, id, isReplication)) {
replicateToPeers(Action.Cancel, appName, id, null, null, 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());
}
}
return true;
}
return false;
}
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的默认阀值因子是85%
,可以通过eureka.renewalPercentThreshold=[0.0, 1.0].
进行修改。
验证完自我保护机制开启后,并不会马上呈现到web上,而是默认需等待 5 分钟(可以通过eureka.server.wait-time-in-ms-when-sync-empty
配置),即 5 分钟后你会看到下面的提示信息:
通过设置eureka.enableSelfPreservation=false
来关闭自我保护功能。
它看到的心跳续订数量已经超过预期的阈值
自我保护关闭
自我保护机制不是一种错误,而是一种Eureka的保护机制。它是有意义的。