什么是自我保护机制?
自我保护机制是一种应对网络异常的安全保护措施。它的架构哲学是宁可保留不健康的微服务,也不轻易注销健康的微服务。
Eureka Server 在运行期间会统计全部服务总体的心跳数,在15分钟内是否低于应该受到的85%。如果低于,则认为是网络异常问题,应该保护,Eureka Server会将当前的实例注册信息保护起来,同时提示一个警告。
一旦进入保护模式:
- Eureka Server将会不再删除服务注册表中的数据。也就是不会注销任何微服务。
- Eureka server仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上,保证当前节点依然可用。
重要变量
类 AbstractInstanceRegistry
//每分钟最小续约阈值
//包含所有客户端,不分服务提供方和消费方
protected volatile int numberOfRenewsPerMinThreshold;
更新方法
protected void updateRenewsPerMinThreshold() {
this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
* (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
* serverConfig.getRenewalPercentThreshold());
}
每分钟最小续约阈值 = 预期中每分钟发送续约消息的客户端数量 * (60s / 每分钟客户端预期续约间隔) * 续约百分比阈值
理解一下这个公式:
- 每分钟最小续约阈值: Eureka Server 每分钟至少要收到这个数量的续约消息(心跳)。
- 预期中每分钟发送续约消息的客户端数量 :注册到 Eureka Server 的客户端的数量。
- 60s / 每分钟客户端预期续约间隔:一分钟内,一个客户端的续约次数。每分钟客户端预期续约间隔,默认是 30s。所以一分钟内,一个客户端的续约次数默认是2次。
- 续约百分比阈值:默认 85%。
更新位置
EurekaServer 初始化
类 EurekaBootstrap
protected void initEurekaServerContext() throws Exception {
EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig();
//...
registry.openForTraffic(applicationInfoManager, registryCount);
//...
}
跟 initEurekaServerContext:
类 PeerAwareInstanceRegistryImpl
@Override
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
// Renewals happen every 30 seconds and for a minute it should be a factor of 2.
//初始化expectedNumberOfClientsSendingRenews
this.expectedNumberOfClientsSendingRenews = count;
//更新numberOfRenewsPerMinThreshold
updateRenewsPerMinThreshold();
//...
}
EurekaClient 主动下线
类 PeerAwareInstanceRegistryImpl
@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);
return true;
}
return false;
}
跟 cancel:
类 AbstractInstanceRegistry
@Override
public boolean cancel(String appName, String id, boolean isReplication) {
return internalCancel(appName, id, isReplication);
}
跟 internalCancel:
protected boolean internalCancel(String appName, String id, boolean isReplication) {
//...
synchronized (lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
//客户端数量减1
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews - 1;
//更新numberOfRenewsPerMinThreshold
updateRenewsPerMinThreshold();
}
}
//...
}
EurekaClient 注册
类 PeerAwareInstanceRegistryImpl
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
super.register(info, leaseDuration, isReplication);
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}
跟 register:
类 AbstractInstanceRegistry
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
//...
synchronized (lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
//客户端数量加1
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
//更新numberOfRenewsPerMinThreshold
updateRenewsPerMinThreshold();
}
}
//...
}
15分钟一次的定时任务
类 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 or if self preservation is disabled.
if ((count) > (serverConfig.getRenewalPercentThreshold() * expectedNumberOfClientsSendingRenews)
|| (!this.isSelfPreservationModeEnabled())) {
this.expectedNumberOfClientsSendingRenews = count;
//更新numberOfRenewsPerMinThreshold
updateRenewsPerMinThreshold();
}
}
logger.info("Current renewal threshold is : {}", numberOfRenewsPerMinThreshold);
} catch (Throwable e) {
logger.error("Cannot update renewal threshold", e);
}
}
自我保护触发任务
类 AbstractInstanceRegistry
protected void postInit() {
renewsLastMin.start();
if (evictionTaskRef.get() != null) {
evictionTaskRef.get().cancel();
}
evictionTaskRef.set(new EvictionTask());
evictionTimer.schedule(evictionTaskRef.get(),
serverConfig.getEvictionIntervalTimerInMs(),
serverConfig.getEvictionIntervalTimerInMs());
}
跟 EvictionTask:
类 EvictionTask in AbstractInstanceRegistry
看 run 方法
class EvictionTask extends TimerTask {
private final AtomicLong lastExecutionNanosRef = new AtomicLong(0l);
@Override
public void run() {
try {
long compensationTimeMs = getCompensationTimeMs();
logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);
evict(compensationTimeMs);
} catch (Throwable e) {
logger.error("Could not run the evict task", e);
}
}
//...
}
public void evict(long additionalLeaseMs) {
logger.debug("Running the evict task");
//如果最后一分钟的续约数量没有达到每分钟最小续约阈值
if (!isLeaseExpirationEnabled()) {
logger.debug("DS: lease expiration is currently disabled.");
//可以保护
return;
}
//...
}
跟 isLeaseExpirationEnabled:
类 PeerAwareInstanceRegistryImpl
@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;
}