天天看點

eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...

eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...

前言

先抛一個問題給我聰明的讀者,如果你們使用微服務

SpringCloud-Netflix

進行業務開發,那麼線上注冊中心肯定也是用了叢集部署,問題來了:

你了解Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移嗎?

可以先思考一分鐘,我希望你能夠帶着問題來閱讀此篇文章,也希望你看完文章後會有所收獲!

背景

前段時間線上

Sentry

平台報警,多個業務服務在和注冊中心互動時,例如續約和系統資料庫增量拉取等都報了

Request execution failed with message : Connection refused

的警告:

eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...

連接配接拒絕.jpg

緊接着又看到

Request execution succeeded on retry #2

的日志。

eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...

連接配接重試.jpg

看到這裡,表明我們的服務在嘗試兩次重連後和注冊中心互動正常了。

一切都顯得那麼有驚無險,這裡報Connection refused 是注冊中心網絡抖動導緻的,接着觸發了我們服務的重連,重連成功後一切又恢複正常。

這次的報警雖然沒有對我們線上業務造成影響,并且也在第一時間恢複了正常,但作為一個愛思考的小火雞,我很好奇這背後的一系列邏輯:

Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?

eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...

問題思考梳理.png

注冊中心叢集負載測試

線上注冊中心是由三台機器組成的叢集,都是

4c8g

的配置,業務端配置注冊中心位址如下(

這裡的peer來代替具體的ip位址

):

//peer1:8080/eureka/,http://peer2:8080/eureka/,http://peer3:8080/eureka/
           

我們可以寫了一個

Demo

進行測試:

注冊中心叢集負載測試

1、本地通過修改

EurekaServer

服務的端口号來模拟注冊中心叢集部署,分别以

8761

8762

兩個端口進行啟動

2、啟動用戶端

SeviceA

,配置注冊中心位址為:

http://localhost:8761/eureka,http://localhost:8762/eureka

eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...

EurekaClient端配置.png

3、啟動

SeviceA

時在發送注冊請求的地方打斷點:

AbstractJerseyEurekaHttpClient.register()

,如下圖所示:

eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...

8761在前.png

這裡看到請求注冊中心時,連接配接的是

8761

這個端口的服務。

4、更改

ServiceA

中注冊中心的配置:

http://localhost:8762/eureka,http://localhost:8761/eureka

5、重新啟動

SeviceA

然後檢視端口,如下圖所示:

eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...

8762在前.png

此時看到請求注冊中心,連接配接的是

8762

這個端口的服務。

注冊中心故障轉移測試

以兩個端口分别啟動

EurekaServer

服務,再啟動一個用戶端

ServiceA

。啟動成功後,關閉一個

8761

端口對應的服務,檢視此時用戶端是否會自動遷移請求到

8762

端口對應的服務:

1、以

8761

8762

兩個端口号啟動

EurekaServer

2、啟動

ServiceA

,配置注冊中心位址為:

http://localhost:8761/eureka,http://localhost:8762/eureka

3、啟動成功後,關閉

8761

端口的

EurekaServer

4、在

EurekaClient

發送心跳請求

的地方打上斷點:

AbstractJerseyEurekaHttpClient.sendHeartBeat()

5、檢視斷點處資料,第一次請求的

EurekaServer

8761

端口的服務,因為該服務已經關閉,是以傳回的

response

null

eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...

8761故障.png

6、第二次會重新請求

8762

端口的服務,傳回的

response

為狀态為

200

,故障轉移成功,如下圖:

eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...

8762故障轉移.png

思考

通過這兩個測試

Demo

,我以為

EurekaClient

每次都會取

defaultZone

配置的第一個

host

作為請求

EurekaServer

的請求的位址,如果該節點故障時,會自動切換配置中的下一個

EurekaServer

進行重新請求。

那麼疑問來了,

EurekaClient

每次請求真的是以配置的

defaultZone

配置的第一個服務節點作為請求的嗎?這似乎也太弱了!!?

EurekaServer

叢集不就成了

僞叢集

!!?除了用戶端配置的第一個節點,其它注冊中心的節點都隻能作為備份和故障轉移來使用!!?

真相是這樣嗎?NO!我們眼見也不一定為實,源碼面前毫無秘密!

翠花,上幹貨!

用戶端請求負載原理

原理圖解

還是先上結論,負載原理如圖所示:

eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...

負載原理.png

這裡會以

EurekaClient

端的

IP

作為随機的種子,然後随機打亂

serverList

,例如我們在商品服務(192.168.10.56)中配置的注冊中心叢集位址為:

peer1,peer2,peer3

,打亂後的位址可能變成

peer3,peer2,peer1

使用者服務(192.168.22.31)中配置的注冊中心叢集位址為:

peer1,peer2,peer3

,打亂後的位址可能變成

peer2,peer1,peer3

EurekaClient

每次請求

serverList

中的第一個服務,進而達到負載的目的。

代碼實作

我們直接看最底層負載代碼的實作,具體代碼在

com.netflix.discovery.shared.resolver.ResolverUtils.randomize()

中:

eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...

代碼實作.png

這裡面

random

是通過我們

EurekaClient

端的

ipv4

做為随機的種子,生成一個重新排序的

serverList

,也就是對應代碼中的

randomList

,是以每個

EurekaClient

擷取到的

serverList

順序可能不同,在使用過程中,取清單的第一個元素作為

server

host

,進而達到負載的目的。

eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...

負載均衡代碼實作.png

思考

原來代碼是通過

EurekaClient

IP

進行負載的,是以剛才通過

DEMO

程式結果就能解釋的通了,因為我們做實驗都是用的同一個

IP

,是以每次都是會通路同一個

Server

節點。

既然說到了負載,這裡肯定會有另一個疑問:

通過IP進行的負載均衡,每次請求都會均勻分散到每一個

Server

節點嗎?

比如第一次通路

Peer1

,第二次通路

Peer2

,第三次通路

Peer3

,第四次繼續通路

Peer1

等,循環往複……

我們可以繼續做個試驗,假如我們有10000個

EurekaClient

節點,3個

EurekaServer

節點。

Client

節點的

IP

區間為:

192.168.0.0 ~ 192.168.255.255

,這裡面共覆寫6w多個

ip

段,測試代碼如下:

/**
 * 模拟注冊中心叢集負載,驗證負載雜湊演算法
 *
 *  @author 一枝花算不算浪漫
 *  @date 2020/6/21 23:36
 */
           

負載測試結果如下:

eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...

負載測試結果.png

可以看到第二個機器會有50%的請求,最後一台機器隻有17%的請求,負載的情況并不是很均勻,我認為通過

IP

負載并不是一個好的方案。

還記得我們之前講過

Ribbon

預設的輪詢算法

RoundRobinRule

,【一起學源碼-微服務】Ribbon 源碼四:進一步探究Ribbon的IRule和IPing 。

這種算法就是一個很好的雜湊演算法,可以保證每次請求都很均勻,原理如下圖:

eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...

Ribbon輪詢算法.png

故障轉移原理

原理圖解

還是先上結論,如下圖:

eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...

故障轉移原理.png

我們的

serverList

按照

client

端的

ip

進行重排序後,每次都會請求第一個元素作為和

Server

端互動的

host

,如果請求失敗,會嘗試請求

serverList

清單中的第二個元素繼續請求,這次請求成功後,會将此次請求的

host

放到全局的一個變量中儲存起來,下次

client

端再次請求 就會直接使用這個

host

這裡最多會重試請求兩次。

代碼實作

直接看底層互動的代碼,位置在

com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient.execute()

中:

eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...

重試代碼.png

我們來分析下這個代碼:

  1. 第101行,擷取

    client

    上次成功

    server

    端的

    host

    ,如果有值則直接使用這個

    host

  2. 第105行,

    getHostCandidates()

    是擷取

    client

    端配置的

    serverList

    資料,且通過

    ip

    進行重排序的清單
  3. 第114行,

    candidateHosts.get(endpointIdx++)

    ,初始

    endpointIdx=0

    ,擷取清單中第1個元素作為

    host

    請求
  4. 第120行,擷取傳回的

    response

    結果,如果傳回的狀态碼是

    200

    ,則将此次請求的

    host

    設定到全局的

    delegate

    變量中
  5. 第133行,執行到這裡說明第120行執行的

    response

    傳回的狀态碼不是

    200

    ,也就是執行失敗,将全局變量

    delegate

    中的資料清空
  6. 再次循環第一步,此時

    endpointIdx=1

    ,擷取清單中的第二個元素作為

    host

    請求
  7. 依次執行,第100行的循環條件

    numberOfRetries=3

    ,最多重試2次就會跳出循環

我們還可以看123和129行,這也正是我們業務抛出來的日志資訊,所有的一切都對應上了。

總結

感謝你看到這裡,相信你已經清楚了開頭提問的問題。

上面已經分析完了

Eureka

叢集下

Client

端請求時負載均衡的選擇以及叢集故障時自動重試請求的實作原理。

eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...
eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...

點個 在看,贊👍支援我吧

eureka叢集隻注冊一個_Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移?...

繼續閱讀