Eureka原理剖析

vchar_fred發表於2021-05-06

Eureka作為微服務中的註冊中心,為微服務叢集間各個服務進行呼叫提供定址的功能,有了它叢集間的服務只需要指定服務名稱就可以了,無需再去關心服務具體部署的伺服器IP,即可正常呼叫。下面來對其中我們開發中會接觸的主要機制的實現原理進行剖析。一些具體細節這裡不做詳細的分析,只關注如下2個大方向的東西:1.註冊相關的機制、2.客戶端和服務端的啟動流程。

Eureka服務端啟動流程

首先需要說明的是eureka server(後面簡稱服務端)是在eureka client(後面簡稱客戶端)的基礎上進一步封裝的一個東西;也就是說客戶端有的東西服務端也有。服務端額外多的東西就是對登錄檔的處理部分。它的啟動流程如下:

  • 初始化環境配置;這個我們在日常開發中幾乎都是使用的預設的;
  • 讀取服務端的配置資訊,也就是讀取eureka-server.properties配置檔案;由於eureka中對這種配置類採用的是面向介面的方式,因此非常好擴充套件,在spring中是重新實現了這些配置類介面的。
  • 構建應用管理器;讀取eureka-client.properties配置檔案,選擇其中的部分配置,基於構造者模式建立服務例項交給應用管理器。
  • 讀取eureka-client.properties配置檔案構建客戶端資訊;這裡的操作和客戶端的啟動流程幾乎就是一樣的,因此這裡就不做詳細說明了。
  • 建立登錄檔感知器registry,這個也可以稱為登錄檔管理器。這裡會維護登錄檔資訊。
  • 建立服務端叢集節點資訊管理器;也就是我們配置的叢集地址資訊,預設10分鐘檢查一次。
  • 基於上面的資訊建立服務端的上下文資訊;這裡在進行初始化的時候會對相關資源進行初始化;啟動相關的定時。
  • 從其他服務端節點上拉取登錄檔資訊;這就是為什麼服務端即使配置fetchRegistry為false,依然可以正常拿到登錄檔資訊。
  • 啟動登錄檔感知器registry的定時;這個定時主要就是檢查登錄檔中是否有過期的註冊資訊。
  • 最後進行監聽器的繫結。

相關的流程圖如下:

Eureka客戶端啟動流程

eureka客戶端的啟動主要就是幾個定時和之後進行登錄檔維護的網路請求資源初始化。

  • 構建應用管理器;讀取eureka-client.properties配置檔案,選擇其中的部分配置,基於構造者模式建立服務例項交給應用管理器。
  • 讀取eureka-client.properties配置資訊構建客戶端的配置資訊;
  • 根據上面的配置資訊和應用管理器構建客戶端;
  • 建立心跳、快取等需要的執行緒池;
  • 建立網路通訊元件;後面傳送註冊資訊、心跳資訊這些請求都是通過它來處理的;
  • 判斷是否需要拉取登錄檔資訊,若是則會全量拉取一次登錄檔資訊;
  • 啟動相關的定時任務:登錄檔更新任務(預設30s執行一次)、心跳定時任務(預設30s執行一次)、建立服務狀態更新定時任務(預設30s執行一次,這個就是留個我們自定義服務上下線狀態的判斷邏輯的);
  • 啟動服務狀態更新定時任務(第一次延遲40s執行);這裡面就是向服務端傳送註冊資訊的實現。
  • 最後進行監聽器的繫結。

相關的流程圖如下:

Eureka登錄檔的原理

eureka的登錄檔中儲存中服務的註冊資訊,下面我們通過如下幾個點來對其原理進行簡析。

登錄檔抓取和快取機制

其基本流程圖如下:

登錄檔的資料結構和快取機制

eureka server中對登錄檔的資訊進行多重快取,分為:

  • 只讀快取(ConcurrentMap):會有定時任務預設每隔30s主動的去和讀寫快取裡面的資訊同步一次;
  • 讀寫快取(guava的LoadingCache):在建立LoadingCache的時候預設設定的過期時間是180s;
  • 登錄檔:這個就是實時的本地註冊資訊,每次客戶端的註冊資訊更新後,都會實時的儲存在這裡;同時在更新它的時候會將讀寫快取中的值設定為失效狀態。

登錄檔資訊讀取流程

登錄檔的拉取分為全量和增量;在初次拉取時使用的是全量,後面使用的都是增量拉取的。

全量拉取流程:

  • 服務端收到客戶端的請求後,會直接從只讀快取裡面取值,如果有就返回,否則進行下一步;
  • 只讀快取裡面沒有時,會從讀寫快取裡面取值,如果有就返回,同時將其設定達到只讀快取裡面;否則進行下一步;
  • 讀寫快取裡面沒有時,會觸發LoadingCache的load方法,這裡面會從本地登錄檔中取值返回。

增量拉取流程:

  • 服務端收到客戶端的請求後,會直接從只讀快取裡面取增量資訊,如果有就返回,否則進行下一步;
  • 只讀快取裡面沒有時,會從讀寫快取裡面取增量資訊,如果有就返回,同時將其設定達到只讀快取裡面;否則進行下一步;
  • 讀寫快取裡面沒有時,會觸發LoadingCache的load方法,這裡面會增量佇列中獲取變化的資訊然後返回;

服務端叢集間的註冊資訊如何同步的

要回答這個問題,我們就需要先了解客戶端傳送註冊資訊和心跳資訊的整個流程,看了下面的註冊和心跳流程這個問題也就可以解釋了。

註冊的流程來說明:

  • 客戶端在啟動的最後一步啟動服務狀態更新定時任務時,裡面的定時任務就會向服務端傳送註冊資訊;
  • 客戶端會選從置的服務服務註冊地址中選擇第一個進行嘗試,如果成功後面都會用這個,直到失敗才會切換到下一個;
  • 服務端收到註冊請求後,更新本地登錄檔中註冊資訊,將讀寫快取中的快取設定為失效狀態;同時將登錄檔的變更資訊儲存到最近變更佇列中;
  • 將註冊請求資訊轉發給eureka server叢集中的其他節點。

心跳的請求也是在服務端自己處理完成後,會自動將這個請求轉發給叢集中的其他節點。心跳的操作就是更新註冊資訊中的租約時間,這裡就不詳細說明了。

注意這種通知叢集中其他節點的操作在失敗後會不斷的重試,同時正式由於有這個操作,因此服務端的fetchRegistry配置為false,叢集間的註冊資訊依然可以正常同步的原因。

客戶端的註冊資訊什麼時候會被摘除

客戶端的註冊資訊被摘除主要是這2種情況:1.客戶端服務主動下線;2.服務異常。

客戶端服務主動下線

客戶端服務下線:主動取消註冊資訊,這種服務端直接接收請求然後刪除即可;其流程圖如下:

服務異常

客戶端異常:沒有傳送取消請求或者是服務端沒有正常接收和處理取消請求的情況下,此時就需要服務端自己定製一套註冊資訊過期機制,這也就是傳送心跳的作用。

服務端中登錄檔資訊過期檢查的定時任務預設每隔60s檢查一次,其大致流程如下:

  • 判斷的過期的依據是:當前時間戳 > (上一次傳送租約的時間戳 + 過期時間(預設90s) + 補充時間(就是距離上一次執行任務的時間超過定時任務配置的60s執行一次的週期時間));但是由於在設定上一次傳送租約的時間戳時候額外加上了一個過期時間;因此最終登錄檔的過期時間就至少是180s。
  • 選擇15%的過期註冊資訊,然後呼叫取消操作來刪除註冊資訊;同時會通知叢集中其他的節點。

Eureka原始碼閱讀建議

spring-cloud-eureka中的server和client是對netflix的eureka進行了封裝,加了一些註解來對spring boot進行支援。因此在閱讀eureka原始碼時,應該先從netflix eureka開始看起,之後再去檢視spring cloud封裝的eureka的原始碼就會輕鬆許多。eureka原始碼地址:https://github.com/Netflix/eureka 、spring-cloud-eureka原始碼地址:https://github.com/spring-cloud/spring-cloud-netflix

建議不要直接從github倉庫裡面去拉取,直接去下載對應版本的壓縮包即可。

網上對eureka原始碼分析的文章有很多,這裡推薦2篇寫得非常不錯的博文:

相關文章