歡迎光臨
每天分享高質量文章

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

點擊上方“芋道原始碼”,選擇“置頂公眾號”

技術文章第一時間送達!

原始碼精品專欄

 

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

  • 1. 概述

  • 2. 定義

  • 3. 實現

  • 3.1 觸發條件

  • 3.2 計算公式

  • 3.3 計算時機

  • 666. 彩蛋


1. 概述

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

推薦 Spring Cloud 書籍

  • 請支持正版。下載盜版,等於主動編寫低級 BUG 。

  • 程式猿DD —— 《Spring Cloud微服務實戰》

  • 周立 —— 《Spring Cloud與Docker微服務架構實戰》

推薦 Spring Cloud 視頻

  • Java 微服務實踐 – Spring Boot

  • Java 微服務實踐 – Spring Cloud

  • Java 微服務實踐 – Spring Boot / 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 計算時機

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

3.3.1 Eureka-Server 初始化

Eureka-Server 在啟動時,從 Eureka-Server 集群獲取註冊信息,並首次初始化 numberOfRenewsPerMinThreshold 、 expectedNumberOfRenewsPerMin 。實現代碼如下:

// 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 定時重新計算 numberOfRenewsPerMinThreshold 、expectedNumberOfRenewsPerMin 。實現代碼如下:

// 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 應用實體註冊

應用實體註冊時,增加 numberOfRenewsPerMinThreshold 、expectedNumberOfRenewsPerMin 。實現代碼如下:

// 
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 應用實體下線

應用實體下線時,減少 numberOfRenewsPerMinThreshold 、expectedNumberOfRenewsPerMin 。實現代碼如下:

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

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


知識星球

目前在知識星球(https://t.zsxq.com/2VbiaEu)更新瞭如下 Dubbo 原始碼解析如下:

01. 除錯環境搭建
02. 專案結構一覽
03. API 配置(一)之應用
04. API 配置(二)之服務提供者
05. API 配置(三)之服務消費者
06. 屬性配置
07. XML 配置
08. 核心流程一覽

一共 60 篇++

赞(0)

分享創造快樂