註冊中心 Eureka 原始碼解析 —— 應用例項註冊發現 (四)之自我保護機制

芋道原始碼_以德服人_不服就幹發表於2018-05-04

摘要: 原創出處 http://www.iocoder.cn/Eureka/instance-registry-self-preservation/ 「芋道原始碼」歡迎轉載,保留摘要,謝謝!

本文主要基於 Eureka 1.8.X 版本


註冊中心 Eureka 原始碼解析 —— 應用例項註冊發現 (四)之自我保護機制

???關注**微信公眾號:【芋道原始碼】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有原始碼分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文註釋原始碼 GitHub 地址
  3. 您對於原始碼的疑問每條留言將得到認真回覆。甚至不知道如何讀原始碼也可以請教噢
  4. 新的原始碼解析文章實時收到通知。每週更新一篇左右
  5. 認真的原始碼交流微信群。

1. 概述

本文主要分享 自我保護機制,為應用例項過期下線做鋪墊。

推薦 Spring Cloud 書籍

推薦 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 2
  • numberOfRenewsPerMinThreshold = expectedNumberOfRenewsPerMin * 續租百分比( eureka.renewalPercentThreshold )

為什麼乘以 2

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

這塊會有一些硬編碼的情況,因此不太建議修改應用例項的續租頻率

為什麼乘以續租百分比

低於這個百分比,意味著開啟自我保護機制。

預設情況下,eureka.renewalPercentThreshold = 0.85

如果你真的調整了續租頻率,可以等比去續租百分比,以保證合適的觸發自我保護機制的閥值。另外,你需要注意,續租頻率是 Client 級別,續租百分比是 Server 級別。

3.3 計算時機

目前有個地方會計算 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin,我們逐小節來看。

3.3.1 Eureka-Server 初始化

Eureka-Server 在啟動時,從 Eureka-Server 叢集獲取註冊資訊,並首次初始化 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin 。實現程式碼如下:

// 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 定時重新計算 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);
   }
}
複製程式碼
  • 配置 eureka.renewalThresholdUpdateIntervalMs 引數,定時重新計算。預設,15 分鐘。
  • 程式碼塊 !this.isSelfPreservationModeEnabled() :當未開啟自我保護機制時,每次都進行重新計算。事實上,這兩個引數不僅僅自我保護機制會使用到,配合 Netflix Servo 實現監控資訊採集 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin
  • 程式碼塊 (count * 2) > (serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold) :當開啟自我保護機制時,應用例項每分鐘最大心跳數( count * 2 ) 小於期望最小每分鐘續租次數( serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold ),不重新計算。如果重新計算,自動保護機制會每次定時執行後失效

3.3.3 應用例項註冊

應用例項註冊時,增加 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin 。實現程式碼如下:

// 
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 應用例項下線

應用例項下線時,減少 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin 。實現程式碼如下:

// 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的自我保護機制》

胖友,分享我的公眾號( 芋道原始碼 ) 給你的胖友可好?

相關文章