Eureka 原理
Eureka 的服務註冊機制
- 新的服務部署完成後,會訪問 Eureka Server,來給自己註冊。
- 每個 Eureka Client,每隔 30 秒,會從 Eureka Server 獲取註冊表的變化,以便及時得知哪些服務已經掛了。
- 每個 Eureka Client,每隔 30 秒,會發送心跳給 Eureka Server,證明自己還活着。
Eureka Server 的訪問頻率
假設現在有 100 個服務,每個服務部署到 20 臺服務器上,機器是標準的 4 核 8G 配置。相當於 100 * 20 = 2000 個服務,也就是 2000 臺服務器。每臺服務器上都有一個 Eureka Client 組件,他們每隔 30 秒都會訪問 2 次 Eureka Server,一次是獲取註冊表的變化,一次是發送心跳。
- 每分鐘:一臺服務器獲取 2 次註冊表的變化,發送 2 次心跳,2000 臺服務器,Eureka Server 就要被訪問 8000 次。
- 每秒:8000 / 60 ≈ 133 次,算上其它一些額外操作,我們算作 200 次左右。
- 每天:8000 * 60 * 24 = 1152 萬次。
對於上述情況,Eureka Server 將要面對日千萬級的訪問量。
Eureka Server 的存儲方式
首先,我們來看看 Eureka 存儲註冊表的源碼。
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
}
- registry:以 ConcurrentHashMap 作爲核心結構,說明註冊表是基於純內存的。key
是服務名稱,如:user-service。value 是服務的多個實例。 - Map<String, Lease<InstanceInfo>>:key 是服務實例的 id,InstanceInfo
存儲服務實例的具體信息,如 IP 地址、端口、hostname 等。 - Lease:維護服務實例的最近心跳時間。
結論:維護註冊表、獲取註冊表的變化、更新心跳時間的操作,全部都發生在內存裏,速度快也就不難理解了。
Eureka 的多級緩存機制
多級緩存儘可能保證註冊表的數據不會頻繁的出現讀寫衝突問題。也提高了速度,性能很高。
- 當 Eureka Client 獲取註冊表時,先讀取 ReadOnlyCacheMap,如果沒有,就去讀 ReadWriteCacheMap,還沒有,就直接讀取註冊表。
- 當註冊表發生變化時,會過期掉 ReadWriteCacheMap,不影響讀取 ReadOnlyCacheMap(30 秒內)。
- 30 秒後,Eureka Server 發現 ReadWriteCacheMap 已經被清空了,就會清空 ReadOnlyCacheMap。
- Eureka Client 再次獲取註冊表時,發現兩個 CacheMap 都是空的,就會直接讀取註冊表,同時填充緩存。
Eureka 的特點總結
- 請求頻率:每 30 秒獲取一次註冊表,每 30 秒發送一次心跳。保證了每秒處理幾百次請求的能力。
- 純內存註冊表:所有請求都可以在內存中處理。保證了高性能。
- 多級緩存機制:避免發生頻繁的讀寫衝突,進一步提升性能。
Eureka 與 Zookeeper
相同點
Eureka 和 Zookeeper 都在分佈式系統中,充當着服務註冊中心的角色。
作爲服務管理組件,都需要具備以下三個能力:
- 熔斷:當檢測到某個服務異常時,要及時把它斷掉,以防整個項目被拖垮。
- 降級:在業務高峯期,爲了保障核心服務,把不重要的服務暫時停掉。
- 限流:面對突然的大量請求,爲了保障服務節點的正常運行,要每隔一段時間,再放一批請求進去。
不同點
Zookeeper 作爲第三方組件,被 Dubbo 使用;而 Eureka,是 Spring Cloud 中的自帶組件。
說到這兒,我們有必要看看 Dubbo 與 Spring Cloud 的區別:
- 服務調用方式:Dubbo 是基於 RPC 調用的;Spring Cloud 是基於 HTTP 協議的 REST API
調用的。所以在遠程調用的效率上,Dubbo 大約是 Spring Cloud 的三倍。 - 框架:Dubbo 是一個微服務治理的框架,依賴於很多第三方插件;而 Spring Cloud 有一套成熟的解決方案。
Zookeeper 放棄了一定的可用性,在服務器掛了後,需要選舉出新的 leader,會產生延遲;而 Eureka 爲了保證可用性,設定每個節點都是公平的,不需要選舉 leader,但也放棄了一致性。
栗子
爲了測試分佈式,我們先配置 host,模擬服務端和客戶端。
127.0.0.1 master
127.0.0.1 slave
127.0.0.1 slave2
創建 Eureka 服務項目(eurekaserver)
- 第一步:選擇 Create New Project,創建一個 Spring Initializr 項目,選擇 Web 組件和 Eureka Server 組件。
- 第二步:在啓動類中加入 Eureka 的服務註解 @EnableEurekaServer。
- 第三步:配置服務屬性。
- 第四步:啓動項目,訪問 127.0.0.1:8761,進入管理界面。
可以看到 EUREKASERVER 服務已經註冊進去了。
創建 Eureka 服務提供項目(user)
- 第一步:選擇 Create New Project,創建一個 Spring Initializr 項目,選擇 Web 組件和 Eureka Discovery 組件。
- 第二步:在啓動類中加入 Eureka 的客戶端註解 @EnableEurekaClient。
- 第三步:配置客戶端屬性。
- 第四步:編寫測試接口。
第五步:啓動項目,訪問 127.0.0.1:8761,進入管理界面。
可以看到 USER 服務也註冊進去了。
創建 Eureka 服務調用項目(customer)
- 第一步:選擇 Create New Project,創建一個 Spring Initializr 項目,選擇 Web 組件和 Eureka Discovery 組件。
- 第二步:在啓動類中加入 Eureka 的客戶端註解 @EnableEurekaClient。
- 第三步:配置客戶端屬性。
- 第四步:調用服務端的接口。
- 第五步:啓動項目,訪問 127.0.0.1:8080/test/testByEureka。
我們可以看到 customer 成功的訪問到了 user 服務。使用 HTTPclient 的方式訪問接口,有點麻煩,我們可以使用 Eureka 中的 RestTemplate,不僅方便,還支持負載均衡。
再啓動一個相同的 user,把端口改爲 8200,訪問 127.0.0.1:8761,進入管理界面。
可以看到現在有2個 USER 服務,端口分別爲 8100 和 8200。
訪問 127.0.0.1:8080/test/testByRestTemplate。
第一次調用了 8200 服務。
刷新時,調用了 8100 服務。
再次刷新,又調用了 8200 服務。(默認分配算法是輪詢)