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

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

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


🙂🙂🙂關注**微信公衆號:【芋道源碼】**有福利:git

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

1. 概述

本文主要分享 自我保護機制,爲應用實例過時下線作鋪墊。github

推薦 Spring Cloud 書籍spring

推薦 Spring Cloud 視頻segmentfault

2. 定義

自我保護機制定義以下:微信

FROM 周立 —— 《理解Eureka的自我保護模式》
當Eureka Server節點在短期內丟失過多客戶端時(可能發生了網絡分區故障),那麼這個節點就會進入自我保護模式。一旦進入該模式,Eureka Server就會保護服務註冊表中的信息,再也不刪除服務註冊表中的數據(也就是不會註銷任何微服務)。當網絡故障恢復後,該Eureka Server節點會自動退出自我保護模式。網絡

爲何使用自動保護機制 ?你也能夠從周立兄的這篇文章獲得答案,這裏筆者就不一本正經的胡說八道了。架構

3. 實現

首先,咱們來看下在自動保護機制裏扮演重要角色的兩個變量:app

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

胖友,分享個人公衆號( 芋道源碼 ) 給你的胖友可好?

相關文章
相關標籤/搜索