⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Eureka/instance-registry-self-preservation/ 「芋道源码」欢迎转载,保留摘要,谢谢!

本文主要基于 Eureka 1.8.X 版本


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

1. 概述

本文主要分享 自我保护机制,为应用实例过期下线做铺垫。

推荐 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 2
  • numberOfRenewsPerMinThreshold = expectedNumberOfRenewsPerMin * 续租百分比( eureka.renewalPercentThreshold )

为什么乘以 2

默认情况下,注册的应用实例每半分钟续租一次,那么一分钟心跳两次,因此 x 2 。

这块会有一些硬编码的情况,因此不太建议修改应用实例的续租频率

为什么乘以续租百分比

低于这个百分比,意味着开启自我保护机制。

默认情况下,eureka.renewalPercentThreshold = 0.85

如果你真的调整了续租频率,可以等比去续租百分比,以保证合适的触发自我保护机制的阀值。另外,你需要注意,续租频率是 Client 级别,续租百分比是 Server 级别。

3.3 计算时机

目前有个地方会计算 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin,我们逐小节来看。

3.3.1 Eureka-Server 初始化

Eureka-Server 在启动时,从 Eureka-Server 集群获取注册信息,并首次初始化 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin 。实现代码如下:

// 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 定时重新计算 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin 。实现代码如下:

// 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 实现监控信息采集 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin
  • 代码块 (count * 2) > (serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold) :当开启自我保护机制时,应用实例每分钟最大心跳数( count * 2 ) 小于期望最小每分钟续租次数( serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold ),不重新计算。如果重新计算,自动保护机制会每次定时执行后失效

3.3.3 应用实例注册

应用实例注册时,增加 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin 。实现代码如下:

// 
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 应用实例下线

应用实例下线时,减少 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin 。实现代码如下:

// 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的自我保护机制》

胖友,分享我的公众号( 芋道源码 ) 给你的胖友可好?

文章目录
  1. 1. 1. 概述
  2. 2. 2. 定义
  3. 3. 3. 实现
    1. 3.1. 3.1 触发条件
    2. 3.2. 3.2 计算公式
    3. 3.3. 3.3 计算时机
      1. 3.3.1. 3.3.1 Eureka-Server 初始化
      2. 3.3.2. 3.3.2 定时重置
      3. 3.3.3. 3.3.3 应用实例注册
      4. 3.3.4. 3.3.4 应用实例下线
  4. 4. 666. 彩蛋