點選上方“芋道原始碼”,選擇“置頂公眾號”
技術文章第一時間送達!
原始碼精品專欄
本文主要基於 Eureka 1.8.X 版本
-
1. 概述
-
2. 定義
-
3. 實現
-
3.1 觸發條件
-
3.2 計算公式
-
3.3 計算時機
-
666. 彩蛋
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
2 -
numberOfRenewsPerMinThreshold
=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的自我保護機制》 。
胖友,分享我的公眾號( 芋道原始碼 ) 給你的胖友可好?
目前在知識星球(https://t.zsxq.com/2VbiaEu)更新瞭如下 Dubbo 原始碼解析如下:
01. 除錯環境搭建
02. 專案結構一覽
03. API 配置(一)之應用
04. API 配置(二)之服務提供者
05. API 配置(三)之服務消費者
06. 屬性配置
07. XML 配置
08. 核心流程一覽
…
一共 60 篇++