全站資源開放下載,感謝廣大網友的支持
鏈接失效請移步職業司平臺
非盈利平臺

非盈利平臺

只為分享一些優質內容

Java幫幫-微信公眾號

Java幫幫-微信公眾號

將分享做到極致

微信小程序

微信小程序

更方便的閱讀

職業司微信公眾號

職業司微信公眾號

實時動態通知

安卓APP

安卓APP

我們從此不分開

程序員生活志-公眾號

程序員生活志-公眾號

程序員生活學習圈,互聯網八卦黑料

支付寶贊助-Java幫幫社區
微信贊助-Java幫幫社區

深入理解Ribbon之源碼解析

23
發表時間:2018-09-05 11:51

什么是Ribbon

Ribbon是Netflix公司開源的一個負載均衡的項目,它屬于上述的第二種,是一個客戶端負載均衡器,運行在客戶端上。它是一個經過了云端測試的IPC庫,可以很好地控制HTTP和TCP客戶端的一些行為。 Feign已經默認使用了Ribbon。

  • 負載均衡

  • 容錯

  • 多協議(HTTP,TCP,UDP)支持異步和反應模型

  • 緩存和批處理

RestTemplate和Ribbon相結合

Ribbon在Netflix組件是非常重要的一個組件,在Zuul中使用Ribbon做負載均衡,以及Feign組件的結合等。在Spring Cloud 中,作為開發中,做的最多的可能是將RestTemplate和Ribbon相結合,你可能會這樣寫:


消費另外一個的服務的接口,差不多是這樣的:



深入理解Ribbon

LoadBalancerClient

在Riibon中一個非常重要的組件為LoadBalancerClient,它作為負載均衡的一個客戶端。它在spring-cloud-commons包下:
的LoadBalancerClient是一個接口,它繼承ServiceInstanceChooser,它的實現類是RibbonLoadBalancerClient,這三者之間的關系如下圖:

image.png

其中LoadBalancerClient接口,有如下三個方法,其中excute()為執行請求,reconstructURI()用來重構url:


ServiceInstanceChooser接口,主要有一個方法,用來根據serviceId來獲取ServiceInstance,代碼如下:



LoadBalancerClient的實現類為RibbonLoadBalancerClient,這個類是非常重要的一個類,最終的負載均衡的請求處理,由它來執行。它的部分源碼如下:



在RibbonLoadBalancerClient的源碼中,其中choose()方法是選擇具體服務實例的一個方法。該方法通過getServer()方法去獲取實例,經過源碼跟蹤,最終交給了ILoadBalancer類去選擇服務實例。

ILoadBalancer在ribbon-loadbalancer的jar包下,它是定義了實現軟件負載均衡的一個接口,它需要一組可供選擇的服務注冊列表信息,以及根據特定方法去選擇服務,它的源碼如下 :


其中,addServers()方法是添加一個Server集合;chooseServer()方法是根據key去獲取Server;markServerDown()方法用來標記某個服務下線;getReachableServers()獲取可用的Server集合;getAllServers()獲取所有的Server集合。

DynamicServerListLoadBalancer

它的繼承類為BaseLoadBalancer,它的實現類為DynamicServerListLoadBalancer,這三者之間的關系如下:

image.png

查看上述三個類的源碼,可用發現,配置以下信息,IClientConfig、IRule、IPing、ServerList、ServerListFilter和ILoadBalancer,查看BaseLoadBalancer類,它默認的情況下,實現了以下配置:

  • IClientConfig ribbonClientConfig: DefaultClientConfigImpl配置

  • IRule ribbonRule: RoundRobinRule 路由策略

  • IPing ribbonPing: DummyPing

  • ServerList ribbonServerList: ConfigurationBasedServerList

  • ServerListFilter ribbonServerListFilter: ZonePreferenceServerListFilter

  • ILoadBalancer ribbonLoadBalancer: ZoneAwareLoadBalancer

IClientConfig 用于對客戶端或者負載均衡的配置,它的默認實現類為DefaultClientConfigImpl。

IRule用于復雜均衡的策略,它有三個方法,其中choose()是根據key 來獲取server,setLoadBalancer()和getLoadBalancer()是用來設置和獲取ILoadBalancer的,它的源碼如下:


IRule有很多默認的實現類,這些實現類根據不同的算法和邏輯來處理負載均衡。Ribbon實現的IRule有一下。在大多數情況下,這些默認的實現類是可以滿足需求的,如果有特性的需求,可以自己實現。

  • BestAvailableRule 選擇最小請求數

  • ClientConfigEnabledRoundRobinRule 輪詢

  • RandomRule 隨機選擇一個server

  • RoundRobinRule 輪詢選擇server

  • RetryRule 根據輪詢的方式重試

  • WeightedResponseTimeRule 根據響應時間去分配一個weight ,weight越低,被選擇的可能性就越低

  • ZoneAvoidanceRule 根據server的zone區域和可用性來輪詢選擇

image.png

IPing是用來想server發生”ping”,來判斷該server是否有響應,從而判斷該server是否可用。它有一個isAlive()方法,它的源碼如下:


IPing的實現類有PingUrl、PingConstant、NoOpPing、DummyPing和NIWSDiscoveryPing。它門之間的關系如下:



  • PingUrl 真實的去ping 某個url,判斷其是否alive

  • PingConstant 固定返回某服務是否可用,默認返回true,即可用

  • NoOpPing 不去ping,直接返回true,即可用。

  • DummyPing 直接返回true,并實現了initWithNiwsConfig方法。

  • NIWSDiscoveryPing,根據DiscoveryEnabledServer的InstanceInfo的InstanceStatus去判斷,如果為InstanceStatus.UP,則為可用,否則不可用。

ServerList是定義獲取所有的server的注冊列表信息的接口,它的代碼如下:


ServerListFilter接口,定于了可根據配置去過濾或者根據特性動態獲取符合條件的server列表的方法,代碼如下:



閱讀DynamicServerListLoadBalancer的源碼,DynamicServerListLoadBalancer的構造函數中有個initWithNiwsConfig()方法。在改方法中,經過一系列的初始化配置,最終執行了restOfInit()方法。其代碼如下:




在restOfInit()方法上,有一個 updateListOfServers()的方法,該方法是用來獲取所有的ServerList的。



進一步跟蹤updateListOfServers()方法的源碼,最終由serverListImpl.getUpdatedListOfServers()獲取所有的服務列表的,代碼如下:



而serverListImpl是ServerList接口的具體實現類。跟蹤代碼,ServerList的實現類為DiscoveryEnabledNIWSServerList,在ribbon-eureka.jar的com.netflix.niws.loadbalancer下。其中DiscoveryEnabledNIWSServerList有 getInitialListOfServers()和getUpdatedListOfServers()方法,具體代碼如下:



繼續跟蹤源碼,obtainServersViaDiscovery(),是根據eurekaClientProvider.get()來回去EurekaClient,再根據EurekaClient來獲取注冊列表信息,代碼如下:



其中eurekaClientProvider的實現類是LegacyEurekaClientProvider,它是一個獲取eurekaClient類,通過靜態的方法去獲取eurekaClient,其代碼如下:



EurekaClient的實現類為DiscoveryClient,在之前已經分析了它具有服務注冊、獲取服務注冊列表等的全部功能。

由此可見,負載均衡器是從EurekaClient獲取服務信息,并根據IRule去路由,并且根據IPing去判斷服務的可用性。

那么現在還有個問題,負載均衡器多久一次去獲取一次從Eureka Client獲取注冊信息呢。

在BaseLoadBalancer類下,BaseLoadBalancer的構造函數,該構造函數開啟了一個PingTask任務,代碼如下:


setupPingTask()的具體代碼邏輯,它開啟了ShutdownEnabledTimer執行PingTask任務,在默認情況下pingIntervalSeconds為10,即每10秒鐘,想EurekaClient發送一次”ping”。



PingTask源碼,即new一個Pinger對象,并執行runPinger()方法。



查看Pinger的runPinger()方法,最終根據 pingerStrategy.pingServers(ping, allServers)來獲取服務的可用性,如果該返回結果,如之前相同,則不去向EurekaClient獲取注冊列表,如果不同則通知ServerStatusChangeListener或者changeListeners發生了改變,進行更新或者重新拉取。



由此可見,LoadBalancerClient是在初始化的時候,會向Eureka回去服務注冊列表,并且向通過10s一次向EurekaClient發送“ping”,來判斷服務的可用性,如果服務的可用性發生了改變或者服務數量和之前的不一致,則更新或者重新拉取。LoadBalancerClient有了這些服務注冊列表,就可以根據具體的IRule來進行負載均衡。

RestTemplate是如何和Ribbon結合的

最后,回答問題的本質,為什么在RestTemplate加一個@LoadBalance注解就可可以開啟負載均衡呢?


全局搜索ctr+shift+f @LoadBalanced有哪些類用到了LoadBalanced有哪些類用到了, 發現LoadBalancerAutoConfiguration類,即LoadBalancer自動配置類。


在該類中,首先維護了一個被@LoadBalanced修飾的RestTemplate對象的List,在初始化的過程中,通過調用customizer.customize(restTemplate)方法來給RestTemplate增加攔截器LoadBalancerInterceptor。

而LoadBalancerInterceptor,用于實時攔截,在LoadBalancerInterceptor這里實現來負載均衡。LoadBalancerInterceptor的攔截方法如下:

總結

綜上所述,Ribbon的負載均衡,主要通過LoadBalancerClient來實現的,而LoadBalancerClient具體交給了ILoadBalancer來處理,ILoadBalancer通過配置IRule、IPing等信息,并向EurekaClient獲取注冊列表的信息,并默認10秒一次向EurekaClient發送“ping”,進而檢查是否更新服務列表,最后,得到注冊列表后,ILoadBalancer根據IRule的策略進行負載均衡。

而RestTemplate 被@LoadBalance注解后,能過用負載均衡,主要是維護了一個被@LoadBalance注解的RestTemplate列表,并給列表中的RestTemplate添加攔截器,進而交給負載均衡器去處理。


Java幫幫學習群生態

Java幫幫學習群生態

總有一款能幫到你

Java學習群

Java學習群

與大牛一起交流

大數據學習群

大數據學習群

在數據中成長

九點編程學習群

九點編程學習群

深夜九點學編程

python學習群

python學習群

人工智能,爬蟲

測試學習群

測試學習群

感受測試的魅力

Java幫幫生態承諾

Java幫幫生態承諾

一直堅守,不負重望

初心
勤儉
誠信
正義
分享
友鏈交換:加幫主QQ2524138991 留言即可 24小時內答復  
業司
教育資訊
會員登錄
獲取驗證碼
登錄
登錄
我的資料
留言
回到頂部