服務發現組件之 — Eureka
- 2020 年 3 月 18 日
- 筆記
前言
現在流行的微服務體系結構正在改變我們構建應用程式的方式,從單一的單體服務轉變為越來越小的可單獨部署的服務(稱為微服務
),共同構成了我們的應用程式。當進行一個業務時不可避免就會存在多個服務之間調用,假如一個服務 A 要訪問在另一台伺服器部署的服務 B,那麼前提是服務 A 要知道服務 B 所在機器的 IP 地址和服務對應的埠,最簡單的方式就是讓服務 A 自己去維護一份服務 B 的配置(包含 IP 地址和埠等資訊),但是這種方式有幾個明顯的缺點:隨著我們調用服務數量的增加,配置文件該如何維護;缺乏靈活性,如果服務 B 改變 IP 地址或者埠,服務 A 也要修改相應的文件配置;還有一個就是進行服務的動態擴容或縮小不方便。
一個比較好的解決方案就是 服務發現(Service Discovery)
。它抽象出來了一個註冊中心,當一個新的服務上線時,它會將自己的 IP 和埠註冊到註冊中心去,會對註冊的服務進行定期的心跳檢測,當發現服務狀態異常時將其從註冊中心剔除下線。服務 A 只要從註冊中心中獲取服務 B 的資訊即可,即使當服務 B 的 IP 或者埠變更了,服務 A 也無需修改,從一定程度上解耦了服務。服務發現目前業界有很多開源的實現,比如 apache
的 zookeeper、 Netflix
的 eureka、hashicorp
的 consul、 CoreOS
的 etcd。
Eureka 是什麼
Eureka
在 github 上對其的定義為
Eureka is a REST (Representational State Transfer) based service that is primarily used in the AWS cloud for locating services for the purpose of load balancing and failover of middle-tier servers.
At Netflix, Eureka is used for the following purposes apart from playing a critical part in mid-tier load balancing.
Eureka
是由 Netflix 公司開源,採用的是 Client / Server 模式進行設計,基於 http 協議和使用 Restful Api 開發的服務註冊與發現組件,提供了完整的服務註冊和服務發現,可以和 Spring Cloud
無縫集成。其中 Server 端扮演著服務註冊中心的角色,主要是為 Client 端提供服務註冊和發現等功能,維護著 Client 端的服務註冊資訊,同時定期心跳檢測已註冊的服務當不可用時將服務剔除下線,Client 端可以通過 Server 端獲取自身所依賴服務的註冊資訊,從而完成服務間的調用。遺憾的是從其官方的 github wiki 可以發現,2.0 版本已經不再開源。但是不影響我們對其進行深入了解,畢竟服務註冊、服務發現相對來說還是比較基礎和通用的,其它開源實現框架的思想也是想通的。
服務註冊中心(Eureka Server)
我們在項目中引入 Eureka Server
的相關依賴,然後在啟動類加上註解 @EnableEurekaServer
,就可以將其作為註冊中心,啟動服務後訪問頁面如下:
我們繼續添加兩個模組 service-provider
,service-consumer
,然後在啟動類加上註解 @EnableEurekaClient
並指定註冊中心地址為我們剛剛啟動的 Eureka Server
,再次訪問可以看到兩個服務都已經註冊進來了。
Demo
倉庫地址:https://github.com/mghio/depth-in-springcloud
可以看到 Eureka
的使用非常簡單,只需要添加幾個註解和配置就實現了服務註冊和服務發現,接下來我們看看它是如何實現這些功能的。
服務註冊(Register)
註冊中心提供了服務註冊介面,用於當有新的服務啟動後進行調用來實現服務註冊,或者心跳檢測到服務狀態異常時,變更對應服務的狀態。服務註冊就是發送一個 POST
請求帶上當前實例資訊到類 ApplicationResource
的 addInstance
方法進行服務註冊。
可以看到方法調用了類 PeerAwareInstanceRegistryImpl
的 register
方法,該方法主要分為兩步:
- 調用父類
AbstractInstanceRegistry
的register
方法把當前服務註冊到註冊中心 - 調用
replicateToPeers
方法使用非同步的方式向其它的Eureka Server
節點同步服務註冊資訊
服務註冊資訊保存在一個嵌套的 map
中,它的結構如下:
第一層 map
的 key
是應用名稱(對應 Demo
里的 SERVICE-PROVIDER
),第二層 map
的 key
是應用對應的實例名稱(對應 Demo
里的 mghio-mbp:service-provider:9999
),一個應用可以有多個實例,主要調用流程如下圖所示:
服務續約(Renew)
服務續約會由服務提供者(比如 Demo
中的 service-provider
)定期調用,類似於心跳,用來告知註冊中心 Eureka Server
自己的狀態,避免被 Eureka Server
認為服務時效將其剔除下線。服務續約就是發送一個 PUT
請求帶上當前實例資訊到類 InstanceResource
的 renewLease
方法進行服務續約操作。
進入到 PeerAwareInstanceRegistryImpl
的 renew
方法可以看到,服務續約步驟大體上和服務註冊一致,先更新當前 Eureka Server
節點的狀態,服務續約成功後再用非同步的方式同步狀態到其它 Eureka Server
節上,主要調用流程如下圖所示:
服務下線(Cancel)
當服務提供者(比如 Demo
中的 service-provider
)停止服務時,會發送請求告知註冊中心 Eureka Server
進行服務剔除下線操作,防止服務消費者從註冊中心調用到不存在的服務。服務下線就是發送一個 DELETE
請求帶上當前實例資訊到類 InstanceResource
的 cancelLease
方法進行服務剔除下線操作。
進入到 PeerAwareInstanceRegistryImpl
的 cancel
方法可以看到,服務續約步驟大體上和服務註冊一致,先在當前 Eureka Server
節點剔除下線該服務,服務下線成功後再用非同步的方式同步狀態到其它 Eureka Server
節上,主要調用流程如下圖所示:
服務剔除(Eviction)
服務剔除是註冊中心 Eureka Server
在啟動時就啟動一個守護執行緒 evictionTimer
來定期(默認為 60
秒)執行檢測服務的,判斷標準就是超過一定時間沒有進行 Renew
的服務,默認的失效時間是 90
秒,也就是說當一個已註冊的服務在 90
秒內沒有向註冊中心 Eureka Server
進行服務續約(Renew),就會被從註冊中心剔除下線。失效時間可以通過配置 eureka.instance.leaseExpirationDurationInSeconds
進行修改,定期執行檢測服務可以通過配置 eureka.server.evictionIntervalTimerInMs
進行修改,主要調用流程如下圖所示:
服務提供者(Service Provider)
對於服務提供方(比如 Demo
中的 service-provider
服務)來說,主要有三大類操作,分別為 服務註冊(Register)
、服務續約(Renew)
、服務下線(Cancel)
,接下來看看這三個操作是如何實現的。
服務註冊(Register)
一個服務要對外提供服務,首先要在註冊中心 Eureka Server
進行服務相關資訊註冊,能進行這一步的前提是你要配置 eureka.client.register-with-eureka=true
,這個默認值為 true
,註冊中心不需要把自己註冊到註冊中心去,把這個配置設為 false
,這個調用比較簡單,主要調用流程如下圖所示:
服務續約(Renew)
服務續約是由服務提供者方定期(默認為 30
秒)發起心跳的,主要是用來告知註冊中心 Eureka Server
自己狀態是正常的還活著,可以通過配置 eureka.instance.lease-renewal-interval-in-seconds
來修改,當然服務續約的前提是要配置 eureka.client.register-with-eureka=true
,將該服務註冊到註冊中心中去,主要調用流程如下圖所示:
服務下線(Cancel)
當服務提供者方服務停止時,要發送 DELETE
請求告知註冊中心 Eureka Server
自己已經下線,好讓註冊中心將自己剔除下線,防止服務消費方從註冊中心獲取到不可用的服務。這個過程實現比較簡單,在類 DiscoveryClient
的 shutdown
方法加上註解 @PreDestroy
,當服務停止時會自動觸發服務剔除下線,執行服務下線邏輯,主要調用流程如下圖所示:
服務消費者(Service Consumer)
這裡的服務消費者如果不需要被其它服務調用的話,其實只會涉及到兩個操作,分別是從註冊中心 獲取服務列表(Fetch)
和 更新服務列表(Update)
。如果同時也需要註冊到註冊中心對外提供服務的話,那麼剩下的過程和上文提到的服務提供者是一致的,這裡不再闡述,接下來看看這兩個操作是如何實現的。
獲取服務列表(Fetch)
服務消費者方啟動之後首先肯定是要先從註冊中心 Eureka Server
獲取到可用的服務列表同時本地也會快取一份。這個獲取服務列表的操作是在服務啟動後 DiscoverClient
類實例化的時候執行的。
可以看出,能發生這個獲取服務列表的操作前提是要保證配置了 eureka.client.fetch-registry=true
,該配置的默認值為 true
,主要調用流程如下圖所示:
更新服務列表(Update)
由上面的 獲取服務列表(Fetch)
操作過程可知,本地也會快取一份,所以這裡需要定期的去到註冊中心 Eureka Server
獲取服務的最新配置,然後比較更新本地快取,這個更新的間隔時間可以通過配置 eureka.client.registry-fetch-interval-seconds
修改,默認為 30
秒,能進行這一步更新服務列表的前提是你要配置 eureka.client.register-with-eureka=true
,這個默認值為 true
。主要調用流程如下圖所示:
總結
工作中項目使用的是 Spring Cloud
技術棧,它有一套非常完善的開源程式碼來整合 Eureka
,使用起來非常方便。之前都是直接加註解和修改幾個配置屬性一氣呵成的,沒有深入了解過源碼實現,本文主要是闡述了服務註冊、服務發現等相關過程和實現方式,對 Eureka
服務發現組件有了更近一步的了解。
參考文章
Netflix Eureka
Service Discovery in a Microservices Architecture