默認狀況下,當EurekaServer在必定時間內(默認90秒)沒有接收到某個客戶端實例的心跳,EurekaServer將會註銷該實例。可是當網絡分區故障發生時,客戶端與EurekaServer之間沒法正常通訊,此時不該該註銷客戶端。Eureka經過「自我保護機制」來解決這個問題:當EurekaServer短期內丟失過多客戶端時,這個節點就會進入自我保護模式。在自我保護模式下,EurekaServer不會剔除任何客戶端。當網絡故障恢復後,該節點會自動退出自我保護模式網絡
自我保護機制的實現是基於維護服務註冊表的類AbstractInstanceRegistry
中的2個變量來維護的app
/**
* 指望最小每分鐘續租次數
*/
protected volatile int numberOfRenewsPerMinThreshold;
/**
* 指望最大每分鐘續租次數
*/
protected volatile int expectedNumberOfRenewsPerMin;複製代碼
服務端的啓動文章能夠看這篇文章:EurekaServer自動裝配及啓動流程解析在服務端啓動、從其餘集羣同步完信息後執行了一個方法:openForTraffic
ide
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
this.expectedNumberOfRenewsPerMin = count * 2;
this.numberOfRenewsPerMinThreshold =
(int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
}複製代碼
指望每分鐘最大續租次數爲:當前服務端已經註冊的客戶端的數量乘2,爲啥呢,由於默認Eureka的續約是30秒指望每分鐘最小續租次數爲:最大續租次數乘續租百分比,默認續租百分比是0.85,也就是說當某個時間窗內若是存在超過百分之十五的客戶端沒有再續租的話則開啓自我保護模式this
DefaultEurekaServerContext
類中有一個initialize
方法,這個方法在執行過程當中會啓動一個定時任務spa
@PostConstruct
@Override
public void initialize() {
logger.info("Initializing ...");
peerEurekaNodes.start();
try {
registry.init(peerEurekaNodes);
} catch (Exception e) {
throw new RuntimeException(e);
}
logger.info("Initialized");
}
public void init(PeerEurekaNodes peerEurekaNodes) throws Exception {
this.numberOfReplicationsLastMin.start();
this.peerEurekaNodes = peerEurekaNodes;
initializedResponseCache();
scheduleRenewalThresholdUpdateTask();
initRemoteRegionRegistry();
try {
Monitors.registerObject(this);
} catch (Throwable e) {
logger.warn("Cannot register the JMX monitor for the InstanceRegistry :", e);
}
}複製代碼
scheduleRenewalThresholdUpdateTask
這個定時任務就是跟自我保護模式相關的了debug
private void scheduleRenewalThresholdUpdateTask() {
timer.schedule(new TimerTask() {
@Override
public void run() {
updateRenewalThreshold();
}
}, serverConfig.getRenewalThresholdUpdateIntervalMs(),
serverConfig.getRenewalThresholdUpdateIntervalMs());
}複製代碼
其中getRenewalThresholdUpdateIntervalMs
默認值是15分鐘code
private void updateRenewalThreshold() {
try {
// 1. 計算應用實例數
Applications apps = eurekaClient.getApplications();
int count = 0;
for (Application app : apps.getRegisteredApplications()) {
for (InstanceInfo instance : app.getInstances()) {
if (this.isRegisterable(instance)) {
++count;
}
}
}
// 2. 計算expectedNumberOfRenewsPerMin 、 numberOfRenewsPerMinThreshold 參數
synchronized (lock) {
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);
}
}複製代碼
分爲2步,第一步就不說了,看第二步當最大續租數量大於最小續租數量時或者沒有開啓自我保護模式時能夠從新計算兩個值,不然不能從新計算server
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
synchronized (lock) {
if (this.expectedNumberOfRenewsPerMin > 0) {
this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2;
this.numberOfRenewsPerMinThreshold =
(int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
}
}
}複製代碼
每當有一個實例註冊上來時,兩個參數都要從新計算,最大指望續租數量+2一樣是由於默認1分鐘續租2次get
public boolean cancel(final String appName, final String id,
final boolean isReplication) {
synchronized (lock) {
if (this.expectedNumberOfRenewsPerMin > 0) {
this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin - 2;
this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
}
}
}複製代碼
於註冊的處理邏輯剛好相反同步
以前在Eureka客戶端續約及服務端過時租約清理源碼解析一文的租約過時清理解析過程當中省略了關於自我保護模式的判斷,如今再看一下。這個判斷在租約過時處理方法的開頭:
public void evict(long additionalLeaseMs) {
logger.debug("Running the evict task");
if (!isLeaseExpirationEnabled()) {
logger.debug("DS: lease expiration is currently disabled.");
return;
}
//....
}複製代碼
詳細內容在isLeaseExpirationEnabled
中
public boolean isLeaseExpirationEnabled() {
if (!isSelfPreservationModeEnabled()) {
return true;
}
return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;
}
public boolean isSelfPreservationModeEnabled() {
return serverConfig.shouldEnableSelfPreservation();
}
public long getNumOfRenewsInLastMin() {
return renewsLastMin.getCount();
}複製代碼
第一個if是判斷是否開啓自我保護模式最後的return則是若是當前最小續租次數大於0,而且最近續約實例數量大於最小期待續租數量