1. 程式人生 > 其它 >Eureka的自我保護

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 定時重新計算 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin 。實現程式碼如下:

// 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做一下同步