摘要: 原創出處 http://www.iocoder.cn/Eureka/instance-registry-self-preservation/ 「芋道原始碼」歡迎轉載,保留摘要,謝謝!
本文主要基於 Eureka 1.8.X 版本
???關注**微信公眾號:【芋道原始碼】**有福利:
- RocketMQ / MyCAT / Sharding-JDBC 所有原始碼分析文章列表
- RocketMQ / MyCAT / Sharding-JDBC 中文註釋原始碼 GitHub 地址
- 您對於原始碼的疑問每條留言都將得到認真回覆。甚至不知道如何讀原始碼也可以請教噢。
- 新的原始碼解析文章實時收到通知。每週更新一篇左右。
- 認真的原始碼交流微信群。
1. 概述
本文主要分享 自我保護機制,為應用例項過期下線做鋪墊。
推薦 Spring Cloud 書籍:
- 請支援正版。下載盜版,等於主動編寫低階 BUG 。
- 程式猿DD —— 《Spring Cloud微服務實戰》
- 周立 —— 《Spring Cloud與Docker微服務架構實戰》
推薦 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的自我保護機制》 。
胖友,分享我的公眾號( 芋道原始碼 ) 給你的胖友可好?