Eureka作為微服務中的注冊中心,為微服務集群間各個服務進行調用提供尋址的功能,有了它集群間的服務只需要指定服務名稱就可以了,無需再去關心服務具體部署的服務器IP,即可正常調用。下面來對其中我們開發中會接觸的主要機制的實現原理進行剖析。一些具體細節這里不做詳細的分析,只關注如下2個大方向的東西:1.注冊相關的機制、2.客戶端和服務端的啟動流程。
Eureka服務端啟動流程
首先需要說明的是eureka server(后面簡稱服務端)是在eureka client(后面簡稱客戶端)的基礎上進一步封裝的一個東西;也就是說客戶端有的東西服務端也有。服務端額外多的東西就是對注冊表的處理部分。它的啟動流程如下:
- 初始化環境配置;這個我們在日常開發中幾乎都是使用的默認的;
- 讀取服務端的配置信息,也就是讀取eureka-server.properties配置文件;由於eureka中對這種配置類采用的是面向接口的方式,因此非常好擴展,在spring中是重新實現了這些配置類接口的。
- 構建應用管理器;讀取eureka-client.properties配置文件,選擇其中的部分配置,基於構造者模式創建服務實例交給應用管理器。
- 讀取eureka-client.properties配置文件構建客戶端信息;這里的操作和客戶端的啟動流程幾乎就是一樣的,因此這里就不做詳細說明了。
- 創建注冊表感知器registry,這個也可以稱為注冊表管理器。這里會維護注冊表信息。
- 創建服務端集群節點信息管理器;也就是我們配置的集群地址信息,默認10分鍾檢查一次。
- 基於上面的信息創建服務端的上下文信息;這里在進行初始化的時候會對相關資源進行初始化;啟動相關的定時。
- 從任意一個其他的服務端節點上拉取注冊表信息;注意這里只有將fetchRegistry配置為true才是有效的(因為這里最終是從服務端中的客戶端中的注冊信息里面拿到的,這也是為什么網上很多說是從任意一個其他節點去拉取,因為客戶端就是選擇的一個去拉取的),同時將registry-sync-retries配置的值大於0(spring中默認值是0,也就是在spring-cloud中是不會執行這一步的)
- 啟動注冊表感知器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篇寫得非常不錯的博文: