Eureka的自我保護
Eureka的自我保護
當Eureka Server節點在短時間內丟失過多客戶端時(可能發生了網路分割槽故障),那麼這個節點就會進入自我保護模式。一旦進入該模式,Eureka Server就會保護服務登錄檔中的資訊,不再刪除服務登錄檔中的資料(也就是不會登出任何微服務)。當網路故障恢復後,該Eureka Server節點會自動退出自我保護模式。
如果想研究自我保護機制,那就要從服務下線那裡,因為自我保護來決定是讓eureka進行剔除服務的,下線程式碼如下:
public void evict(long additionalLeaseMs) { logger.debug("Running the evict task"); //是否允許主動摘除故障的服務例項 if (!isLeaseExpirationEnabled()) { logger.debug("DS: lease expiration is currently disabled."); return; } .... }
@Override public boolean isLeaseExpirationEnabled() { //預設是true,關閉自我保護機制的話,這個方法永遠返回true,隨時都可以清除故障的服務例項。 if (!isSelfPreservationModeEnabled()) { // The self preservation mode is disabled, hence allowing the instances to expire. return true; } //numberOfRenewsPerMinThreshold 這個是我期望的一分鐘傳送多少心跳。 //getNumOfRenewsInLastMin 這個是上一分鐘一共發來多少心跳 return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold; }
觸發的機制就是說,如果最近一分鐘發過來的心跳小於期望的值,這進入心跳保護機制。那麼就來看一下,這個期望的值是什麼算出來的。
首先在初始化的時候,就會給他先賦值,預設值為其他server拉著的登錄檔資訊的數量乘以2
// 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());
然後再上線,下線,故障的時候都會對他進行修改。
由於以前的版本為硬編碼,是直接乘以2的,近期的版本,對程式碼進行了優化,因為心跳是30秒發來一次,所以直接用60/30,然後在乘以0.85。
為什麼乘以續租百分比
低於這個百分比,意味著開啟自我保護機制。默情況下,eureka.renewalPercentThreshold = 0.85
。
如果你真的調整了續租頻率,可以等比去續租百分比,以保證合適的觸發自我保護機制的閥值。另外,你需要注意,續租頻率是 Client 級別,續租百分比是 Server 級別。
protected void updateRenewsPerMinThreshold() {
this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
* (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
* serverConfig.getRenewalPercentThreshold());
}
以前的程式碼是有bug的,只對下線的程式碼進行了修改,故障是沒有的,近期版本也做了修復。把剔除服務的相關程式碼都抽取了出來了,形成了internalCancel方法,被下面的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);
}
}
- Registry登錄檔,預設是15分鐘,會跑一次定時任務,算一下服務例項的數量,如果從別的eureka server拉取到的服務例項的數量,大於當前的服務例項的數量,會重新計算一下,主要是跟其他的eureka server做一下同步