本文主要基于 Eureka 1.8.X 版本
- 概述
- 定义
- 实现
- 3.1 触发条件
- 3.2 计算公式
- 3.3 计算时机
- 彩蛋
1. 概述
本文主要分享 自我保护机制,为应用实例过期下线做铺垫。
推荐 Spring Cloud 书籍:
- 请支持正版。下载盗版,等于主动编写低级 BUG 。
- 程序猿DD —— 《Spring Cloud微服务实战》
- 周立 —— 《Spring Cloud与Docker微服务架构实战》
推荐 Spring Cloud 视频:
- Java 微服务实践 - Spring Boot
- Java 微服务实践 - Spring Cloud
- Java 微服务实践 - Spring Boot / Spring Cloud
2. 定义
自我保护机制定义如下:
FROM 周立 —— 《理解Eureka的自我保护模式》 当Eureka Server节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,Eureka Server就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。
为什么使用自动保护机制 ?你也可以从周立兄的这篇文章得到答案,这里笔者就不一本正经的胡说八道了。
3. 实现
首先,我们来看下在自动保护机制里扮演重要角色的两个变量:
// AbstractInstanceRegistry.java
/**
* 期望最小每分钟续租次数
*/
protected volatile int numberOfRenewsPerMinThreshold;
/**
* 期望最大每分钟续租次数
*/
protected volatile int expectedNumberOfRenewsPerMin;
expectedNumberOfRenewsPerMin
,期望最大每分钟续租次数。numberOfRenewsPerMinThreshold
,期望最小每分钟续租次数。
3.1 触发条件
当每分钟心跳次数(
renewsLastMin
) 小于
numberOfRenewsPerMinThreshold
时,并且开启自动保护模式开关(
eureka.enableSelfPreservation = true
) 时,触发自动保护机制,不再自动过期租约,实现代码如下:
// AbstractInstanceRegistry.java
public void evict(long additionalLeaseMs) {
if (!isLeaseExpirationEnabled()) {
logger.debug("DS: lease expiration is currently disabled.");
return;
}
// ... 省略过期租约逻辑
}
// PeerAwareInstanceRegistryImpl.java
@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;
}
3.2 计算公式
计算公式如下:
expectedNumberOfRenewsPerMin
= 当前注册的应用实例数x
2numberOfRenewsPerMinThreshold
=expectedNumberOfRenewsPerMin
*
续租百分比(eureka.renewalPercentThreshold
)
为什么乘以 2
默认情况下,注册的应用实例每半分钟续租一次,那么一分钟心跳两次,因此 x 2 。
这块会有一些硬编码的情况,因此不太建议修改应用实例的续租频率。
为什么乘以续租百分比
低于这个百分比,意味着开启自我保护机制。
默认情况下,
eureka.renewalPercentThreshold = 0.85
。
如果你真的调整了续租频率,可以等比去续租百分比,以保证合适的触发自我保护机制的阀值。另外,你需要注意,续租频率是 Client 级别,续租百分比是 Server 级别。
3.3 计算时机
目前有四个地方会计算
numberOfRenewsPerMinThreshold
、
expectedNumberOfRenewsPerMin
,我们逐小节来看。
3.3.1 Eureka-Server 初始化
Eureka-Server 在启动时,从 Eureka-Server 集群获取注册信息,并首次初始化
numberOfRenewsPerMinThreshold
、
expectedNumberOfRenewsPerMin
。实现代码如下:
// EurekaBootStrap.java
protected void initEurekaServerContext() throws Exception {
// ... 省略其它代码
// 【2.2.10】从其他 Eureka-Server 拉取注册信息
// Copy registry from neighboring eureka node
int registryCount = registry.syncUp();
registry.openForTraffic(applicationInfoManager, registryCount);
// ... 省略其它代码
}
// PeerAwareInstanceRegistryImpl.java
@Override
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
// Renewals happen every 30 seconds and for a minute it should be a factor of 2.
this.expectedNumberOfRenewsPerMin = count * 2;
this.numberOfRenewsPerMinThreshold =
(int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
// ... 省略其它代码
}
3.3.2 定时重置
Eureka-Server 定时重新计算
numberOfRenewsPerMinThreshold
、
expectedNumberOfRenewsPerMin
。实现代码如下:
// PeerAwareInstanceRegistryImpl.java
private void scheduleRenewalThresholdUpdateTask() {
timer.schedule(new TimerTask() {
@Override
public void run() {
updateRenewalThreshold();
}
}, serverConfig.getRenewalThresholdUpdateIntervalMs(),
serverConfig.getRenewalThresholdUpdateIntervalMs());
}
// AbstractInstanceRegistry.java
/**
* 自我保护机锁
*
* 当计算如下参数时使用:
* 1. {@link #numberOfRenewsPerMinThreshold}
* 2. {@link #expectedNumberOfRenewsPerMin}
*/
protected final Object lock = new Object();
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;
}
}
}
// 计算 expectedNumberOfRenewsPerMin 、 numberOfRenewsPerMinThreshold 参数
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);
}
}
- 配置
eureka.renewalThresholdUpdateIntervalMs
参数,定时重新计算。默认,15 分钟。 - 代码块
!this.isSelfPreservationModeEnabled()
:当未开启自我保护机制时,每次都进行重新计算。事实上,这两个参数不仅仅自我保护机制会使用到,配合 Netflix Servo 实现监控信息采集numberOfRenewsPerMinThreshold
、expectedNumberOfRenewsPerMin
。 - 代码块
(count * 2) (serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold)
:当开启自我保护机制时,应用实例每分钟最大心跳数(count * 2
) 小于期望最小每分钟续租次数(serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold
),不重新计算。如果重新计算,自动保护机制会每次定时执行后失效。
3.3.3 应用实例注册
应用实例注册时,增加
numberOfRenewsPerMinThreshold
、
expectedNumberOfRenewsPerMin
。实现代码如下:
//
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
// ... 省略无关代码
// The lease does not exist and hence it is a new registration
// 【自我保护机制】增加 `numberOfRenewsPerMinThreshold` 、`expectedNumberOfRenewsPerMin`
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());
}
}
// ... 省略无关代码
}
3.3.4 应用实例下线
应用实例下线时,减少
numberOfRenewsPerMinThreshold
、
expectedNumberOfRenewsPerMin
。实现代码如下:
// PeerAwareInstanceRegistryImpl.java
@Override
public boolean cancel(final String appName, final String id,
final 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());
}
}
// ... 省略无关代码
}
666. 彩蛋知识星球
😈 终于完整理解 Eureka-Server 自我保护机制,满足。噶~~
推荐另一篇 Eureka-Server 自我保护机制源码分析文章:《理解eureka的自我保护机制》 。
胖友,分享我的公众号( 芋道源码 ) 给你的胖友可好?