前言
先抛一個問題給我聰明的讀者,如果你們使用微服務
SpringCloud-Netflix
進行業務開發,那麼線上注冊中心肯定也是用了叢集部署,問題來了:
你了解Eureka注冊中心叢集如何實作用戶端請求負載及故障轉移嗎?
可以先思考一分鐘,我希望你能夠帶着問題來閱讀此篇文章,也希望你看完文章後會有所收獲!
背景
前段時間線上
Sentry
平台報警,多個業務服務在和注冊中心互動時,例如續約和系統資料庫增量拉取等都報了
Request execution failed with message : Connection refused
的警告:
連接配接拒絕.jpg
緊接着又看到
Request execution succeeded on retry #2
的日志。
連接配接重試.jpg
看到這裡,表明我們的服務在嘗試兩次重連後和注冊中心互動正常了。
一切都顯得那麼有驚無險,這裡報Connection refused 是注冊中心網絡抖動導緻的,接着觸發了我們服務的重連,重連成功後一切又恢複正常。
這次的報警雖然沒有對我們線上業務造成影響,并且也在第一時間恢複了正常,但作為一個愛思考的小火雞,我很好奇這背後的一系列邏輯:
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
EurekaClient端配置.png
3、啟動
SeviceA
時在發送注冊請求的地方打斷點:
AbstractJerseyEurekaHttpClient.register()
,如下圖所示:
8761在前.png
這裡看到請求注冊中心時,連接配接的是
8761
這個端口的服務。
4、更改
ServiceA
中注冊中心的配置:
http://localhost:8762/eureka,http://localhost:8761/eureka
5、重新啟動
SeviceA
然後檢視端口,如下圖所示:
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
8761故障.png
6、第二次會重新請求
8762
端口的服務,傳回的
response
為狀态為
200
,故障轉移成功,如下圖:
8762故障轉移.png
思考
通過這兩個測試
Demo
,我以為
EurekaClient
每次都會取
defaultZone
配置的第一個
host
作為請求
EurekaServer
的請求的位址,如果該節點故障時,會自動切換配置中的下一個
EurekaServer
進行重新請求。
那麼疑問來了,
EurekaClient
每次請求真的是以配置的
defaultZone
配置的第一個服務節點作為請求的嗎?這似乎也太弱了!!?
EurekaServer
叢集不就成了
僞叢集
!!?除了用戶端配置的第一個節點,其它注冊中心的節點都隻能作為備份和故障轉移來使用!!?
真相是這樣嗎?NO!我們眼見也不一定為實,源碼面前毫無秘密!
翠花,上幹貨!
用戶端請求負載原理
原理圖解
還是先上結論,負載原理如圖所示:
負載原理.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()
中:
代碼實作.png
這裡面
random
是通過我們
EurekaClient
端的
ipv4
做為随機的種子,生成一個重新排序的
serverList
,也就是對應代碼中的
randomList
,是以每個
EurekaClient
擷取到的
serverList
順序可能不同,在使用過程中,取清單的第一個元素作為
server
端
host
,進而達到負載的目的。
負載均衡代碼實作.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
*/
負載測試結果如下:
負載測試結果.png
可以看到第二個機器會有50%的請求,最後一台機器隻有17%的請求,負載的情況并不是很均勻,我認為通過
IP
負載并不是一個好的方案。
還記得我們之前講過
Ribbon
預設的輪詢算法
RoundRobinRule
,【一起學源碼-微服務】Ribbon 源碼四:進一步探究Ribbon的IRule和IPing 。
這種算法就是一個很好的雜湊演算法,可以保證每次請求都很均勻,原理如下圖:
Ribbon輪詢算法.png
故障轉移原理
原理圖解
還是先上結論,如下圖:
故障轉移原理.png
我們的
serverList
按照
client
端的
ip
進行重排序後,每次都會請求第一個元素作為和
Server
端互動的
host
,如果請求失敗,會嘗試請求
serverList
清單中的第二個元素繼續請求,這次請求成功後,會将此次請求的
host
放到全局的一個變量中儲存起來,下次
client
端再次請求 就會直接使用這個
host
。
這裡最多會重試請求兩次。
代碼實作
直接看底層互動的代碼,位置在
com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient.execute()
中:
重試代碼.png
我們來分析下這個代碼:
- 第101行,擷取
上次成功client
端的server
,如果有值則直接使用這個host
host
- 第105行,
是擷取getHostCandidates()
端配置的client
資料,且通過serverList
進行重排序的清單ip
- 第114行,
,初始candidateHosts.get(endpointIdx++)
,擷取清單中第1個元素作為endpointIdx=0
請求host
- 第120行,擷取傳回的
結果,如果傳回的狀态碼是response
,則将此次請求的200
設定到全局的host
變量中delegate
- 第133行,執行到這裡說明第120行執行的
傳回的狀态碼不是response
,也就是執行失敗,将全局變量200
中的資料清空delegate
- 再次循環第一步,此時
,擷取清單中的第二個元素作為endpointIdx=1
請求host
- 依次執行,第100行的循環條件
,最多重試2次就會跳出循環numberOfRetries=3
我們還可以看123和129行,這也正是我們業務抛出來的日志資訊,所有的一切都對應上了。
總結
感謝你看到這裡,相信你已經清楚了開頭提問的問題。
上面已經分析完了
Eureka
叢集下
Client
端請求時負載均衡的選擇以及叢集故障時自動重試請求的實作原理。
點個 在看,贊👍支援我吧