Spring Cloud Eureka原理分析(二):續租、下線、自我保護機制和自動清理(服務端)

xinlmain發表於2018-12-06

續租、下線等操作比較直觀,實際上也不復雜。讓我們自己想想它們大概會在服務端有什麼操作。

  • renew: 更新Lease的lastUpdateTimestamp, 更新一下InstanceInfo的最新狀態。然後呼叫其他同伴節點的renew介面。
  • cancel:把lease從registry中移除,設定lease的evictionTimestamp,然後設定InstanceInfo為已刪除。然後把lease加入到recentlyChangedQueue中。最後呼叫同伴節點的cancel介面。

—— 服務端確實做了差不多這些事情。因此為了不影響其他重要事件,這裡不再繼續深入。

自我保護機制

Eureka選擇做一個支援AP的系統(CAP定理)。其實很好理解,當發生大量應用例項不再renew時,服務端認為發生了網路分割槽。Eureka為了保證高可用,不再踢掉已經到期的lease,從而讓依賴於該服務端例項的client端仍然能正常進行服務發現(儘管存在服務例項確實掛掉的可能,即犧牲了一致性)。

在Spring Cloud Eureka的Home頁面上面經常會見到這個警告,就是啟用了自我保護機制。

Spring Cloud Eureka原理分析(二):續租、下線、自我保護機制和自動清理(服務端)

關於這個問題網上的解釋也很多了,下面這篇文章提供了幾幅有用的圖:

The Mystery of Eureka Self-Preservation

實現細節

這篇文章有所有你想要的。 Eureka 原始碼解析 —— 應用例項註冊發現(四)之自我保護機制

搬過來一些重點:

1. 觸發條件

看程式碼

// 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;
}
複製程式碼

當每分鐘心跳次數( renewsLastMin ) 小於 numberOfRenewsPerMinThreshold 時,並且開啟自動保護模式開關( eureka.server.enableSelfPreservation = true ) 時,觸發自動保護機制,不再自動過期租約。

其中:

2. 計算公式

  • numberOfRenewsPerMinThreshold = expectedNumberOfRenewsPerMin * 續租百分比( eureka.server.renewalPercentThreshold, 預設0.85 )
  • expectedNumberOfRenewsPerMin = 當前註冊的應用例項數 x 2

為什麼乘以 2:

預設情況下,註冊的應用例項每半分鐘續租一次,那麼一分鐘心跳兩次,因此 x 2 。

這裡有硬編碼的情況,因此修改應用例項的續租頻率會讓計算不太準。不過,自我保護機制我比較懷疑它的重要性,該調還得調。

3. 計算時機

有四個地方會重新計算numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin

  • Eureka-Server 啟動時。先從相鄰節點同步registry中的資訊,得到已註冊的例項數量,然後套公式計算。
  • 定時重新計算。計算方法同上類似。頻率由eureka.server.renewalThresholdUpdateIntervalMs控制。
  • 應用例項註冊時,增加了註冊例項數,所以要重算。
  • 例項下線時,同理。

自動清理機制

清理過期lease的也是一個定時任務EvictionTask,頻率由eureka.server.evictionIntervalTimerInMs,預設為60秒。

1. EvictionTask程式碼

class EvictionTask extends TimerTask {

   @Override
   public void run() {
       try {
           // 獲取 補償時間毫秒數
           long compensationTimeMs = getCompensationTimeMs();
           logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);
           // 清理過期租約邏輯
           evict(compensationTimeMs);
       } catch (Throwable e) {
           logger.error("Could not run the evict task", e);
       }
   }
}
複製程式碼

那個getCompensationTimeMs()方法的意思是,定時器可能比設定的時間更晚觸發,原因可能是GC等。假設本來要在上一次執行後60s再次觸發,但因為GC在第70秒才被觸發,這時去檢查有沒有lease有沒有超期無renew不能用70s來算,而應該還用60s來算,因為在這10s的GC時間中,很可能此服務端無法處理註冊請求。補償的10s就是這個意思,就是允許例項多超期這10s。

2. 清理邏輯

evict(compensationTime)又比較長,下面分段分析。

  1. 判斷是不是啟用了自我保護機制,如是則不再清理。
  2. 遍歷registry中的所有例項,比較當前時間和lastUpdatedTime + duration + compensationTime,如果前者大,說明已過期。
  3. 計算最大可清理的例項數量。不得讓清理後的例項數量低於當前數量 * eureka.server.renewalPercentThreshold,預設又是0.85。這個閾值還是很高的,如果有大量例項過期,就需要分多批執行才能清理完。
  4. 隨機清理過期的租約。由於租約是按照應用順序新增到陣列,通過隨機的方式,儘量避免單個應用被全部過期。
  5. 最後,呼叫internalCancel處理每個要被清理的lease,這個方法就是前面的cancel階段會調的。

這個自動清理似乎沒有告知同伴節點,大家各做各的。可見Eureka的一致性是蠻低的。

相關文章