為了深入理解Ribbon,現在從源碼的角度來講解Ribbon,看它如何和Eureka相結合,並如何和RestTemplate相結合實現負載均衡。首先跟蹤LoadBalancerClient的源碼,它是一個接口類,繼承了ServiceInstanceChooser,它的實現類為RibbonLoadBalancerClient,它們之間的關係如下圖:
LoadBalancerClient是一個負載均衡的客戶端,有如下3個方法,其中2個execute()方法,均是用來執行請求的,reconstructURI()是用來重構URL的,代碼如下:
ServiceInstanceChooser接口有一個方法用於根據serviceId獲取ServiceInstance,即根據服務名來選擇服務實例,代碼如下:
LoadBalancerClient的實現類為RibbonLoadBalancerClient。RibbonLoadBalancerClient是一個非常重要的類,最終的負載均衡的處理由它來處理。RibbonLoadBalancerClient的部分源碼如下:
RibbonLoadBalancerClient的源碼中,choose()方法用來選擇具體的服務實例。該方法通過getServer()方法去獲取實例,經過源碼跟蹤,最終交給ILoadBalancer類去選擇服務實例。
ILoadBalancer在Ribbon-loadbalancer的jar包下,ILoadBalancer是一個接口,該接口定義了一系列實現負載均衡的方法,源碼如下:
addServers():用於添加一個Server集合。chooseServer():用於根據key去獲取Server。markServerDown():用於標記某個服務下線。getReachableServers():獲取可用的Server集合。getAllServers():獲取所有的Server集合。
ILoadBalancer的子類為BaseLoadBalancer,BaseLoadBalancer的實現類為DynamicServerListLoadBalancer,三者之間的關係如下圖:
查看DynamicServerListLoadBalancer類的源碼,DynamicServerListLoadBalancer需要配置IClientConfig、IRule、IPing、ServerList、ServerListFilter和ILoadBalancer。查看BaseLoadBalancer類的源碼,在默認的情況下,實現了如下配置。
IClientConfig ribbonIClientConfig:DefaultIClientConfigImplIRule ribbonRule:RoundRibbonRuleIPing ribbonPing:DummyPingServerList ribbonServerList:ConfigurationBasedServerListServerListFilter ribbonServerListFilter:ZonePreferenceServerListFilterILoadBalancer ribbonILoadBalancer:ZoneAwareLoadBalancer
IClientConfig用於配置負載均衡的客戶端,IClientConfig的默認實現類為DefaultIClientConfigImpl。
IRule用於配置負載均衡的策略,IRule有3個方法,其中choose()方法根據key來獲取server實例的,setLoadBalancer()和getLoadBalancer()是用來設置和獲取ILoadBalancer的,它的源碼如下:
IRule有很多的默認實現類,這些實現類根據不同的算法和邏輯來處理負載均衡的策略。IRule的默認實現類有以下7種。在大多數情況下,這些默認的實現類是可以滿足需求的,如果有特殊的需求,可以自己實現。IRule和實現類之間的關係圖如下。
BestAvailableRule:選擇最小請求值。ClientConfigEnableRoundRobinRule:輪循。RandomRule:隨機選擇一個server。RoundRobinRule:輪循選擇server。RetryRule:根據輪循的方式重試。WeightedResponseTimeRule:根據響應時間去分配一個weight,weight越低,被選擇的可能性就越低。ZoneAvoidanceRule:根據server的zone區域和可用性來輪循選擇。
IPing用於向server發送「ping」,來判斷server是否有響應,從而判斷該server是否可用。它有一個isAlive()方法,源碼如下:
IPing的實現類有PingUrl、PingConstant、NoOpPing、DummyPing和NIWSDiscoveryPing。他們之間的關係如下圖:
PingUrl:真實地去ping某個URL,判斷其是否可用。PingConstant:固定返回某服務是否可用,默認返回true,即可用。NoOpPing:不去ping,直接返回true,即可用。DummyPing:直接返回true,並實現了initWithNiewsConfig()方法。NIWSDiscoveryPing:根據DiscoveryEnableServer的InstanceInfo的InstanceStatus去判斷,如果為InstanceStatus.UP,則可用,否則不可用。
ServerList是定義獲取所有server的註冊列表信息的接口,它的代碼如下:
ServerListFilter接口定義了可根據配置去過濾或者特性動態的獲取符合條件的server列表的方法,代碼如下:
閱讀DynamicServerListLoadBalancer的源碼,DynamicServerListLoadBalancer的構造函數中有一個initWithNiewsConfig()方法。在該方法中經過一系列的初始化配置,最終執行了restOfInit()方法。DynamicServerListLoadBalancer的部分源碼如下:
在restOfInit()方法中,有一個updateListOfServers()的方法,該方法是用來獲取所有的ServerList的。
進一步跟蹤updateListOfServers()方法的源碼,最終由serverListImpl.getUpdatedListOfServers()獲取所有的服務列表,代碼如下:
而serverListImpl是ServerList接口的具體實現類。跟蹤源碼,ServerList的實現類為DiscoveryEnableNIEWSServerList,這個類在ribbon-eureka.jar的com.netflix.niews.loadbalancer包下。其中,DiscoveryEnableNIEWSServerList有getInitialListOfServers()和getUpdatedListOfServers()的方法,具體代碼如下:
繼續跟蹤源碼,obtainServersViaDiscovery()方法是根據eurekaClientProvider.get()方法來獲取EurekaClient的,在根據EurekaClient來獲取服務註冊表列信息,代碼如下:
其中,eurekaClientProvider的實現類是LegacyEurekaClientProvider,LegacyEurekaClientProvider是一個獲取eurekaClient實例的類,其中代碼如下:
EurekaClient的實現類為DiscoveryClient,DiscoveryClient具有服務註冊、獲取服務註冊列表等功能。
由此可見,負載均衡是從Eureka Client獲取服務列表信息的,並根據IRule的策略去路由,根據IPing去判斷服務的可用性。
那麼還有一個問題,負載均衡器沒個多長時間從Eureka Client獲取註冊信息呢?
在BaseLoadBalancer類的源碼中,在BaseLoadBalancer的構造方法開啟了一個PingTask任務,代碼如下:
在setupPingTask()的具體代碼邏輯裡,開啟了ShutdownEnabledTimer的PingTask任務,在默認情況下,變量pingIntervalSeconds的值為10,即每10秒向Eureka Client發送一次心跳「ping」。
在PingTask的源碼,PingTask創建了一個Pinger對象,並執行了runPinger()方法。
查看Pinger的runPinger()方法,最終根據pingerStrategy.pingServers(ping, allServers)來獲取服務的可用性,如果該返回結果與之前相同,則不向Eureka Client獲取註冊列表;如果不同,則通知ServerStatusChangeListener服務註冊列表信息發生了改變,進行更新或者重新拉取,代碼如下:
由此可見,LoadBalancerClient是在初始化時向Eureka獲取服務註冊列表信息,並且每10秒向EurekaClient發送「ping」,來判斷服務的可用性。如果服務的可用性發生了改變或者服務的數量和之前不一致,則更新或者重新拉取。LoadBalancerClient有了這些服務註冊列表信息,就可以根據具體IRule的策略來進行負載均衡。
最後,回到問題的本身,為什麼在RestTemplate類的bean上加上一個@LoadBalanced註解就可以使用Ribbon的負載均衡呢?
全局搜索查看哪些類使用了@LoadBalanced註解。通過搜索,可以發現LoadBalancerAutoConfiguration類(LoadBalancer的自動配置類)使用到了該註解,LoadBalancerAutoConfiguration類的代碼如下:
在LoadBalancerAutoConfiguration類中,首先維護了一個被@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加上@LoadBalanced註解後,在遠程調度時能夠負載均衡,主要維護了一個被@LoadBalanced註解的RestTemplate列表,並給該列表中的RestTemplate對象添加了攔截器。在攔截器的方法中,將遠程調度方法交給了Ribbon的負載均衡器LoadBalancerClient去處理,從而達到了負載均衡的目的。