Ribbon核心源碼解析(二)- ILoadBalancer組件
在上一篇文章中,我們介紹了Ribbon中的調用流程與負載均衡過程,本文我們再來詳細看一看它的核心組件ILoadBalancer。
核心組件ILoadBalancer
返回服務實例的調用過程大體已經了解了,但是我們剛才略過了一個內容,就是獲取LoadBalancer的過程,回去看第一次調用的getServer方法:
這里通過getLoadBalancer方法返回一個ILoadBalancer負載均衡器,具體調用了Spring的BeanFactoryUtil,通過getBean方法從spring容器中獲取類型匹配的bean實例:
回到前面getServer方法調用的那張圖,你就會發現這時候已經返回了一個ZoneAwareLoadBalancer,并且其中已經保存好了服務列表。看一下ILoadBalancer 的接口定義:
public interface ILoadBalancer { //往該ILoadBalancer中添加服務 public void addServers(List
該接口定義了Ribbon中核心的兩項內容,服務獲取與服務選擇,可以說,ILoadBalancer是Ribbon中最重要的一個組件,它起到了承上啟下的作用,既要連接 Eureka獲取服務地址,又要調用IRule利用負載均衡算法選擇服務。下面分別介紹。
服務獲取
Ribbon在選擇之前需要獲取服務列表,而Ribbon本身不具有服務發現的功能,所以需要借助Eureka來解決獲取服務列表的問題。回到文章開頭說到的配置類RibbonEurekaAutoConfiguration:
@Configuration @EnableConfigurationProperties @ConditionalOnRibbonAndEurekaEnabled @AutoConfigureAfter(RibbonAutoConfiguration.class) @RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class) public class RibbonEurekaAutoConfiguration { }
其中定義了其默認配置類為EurekaRibbonClientConfiguration,在它的ribbonServerList方法中創建了服務發現組件DiscoveryEnabledNIWSServerList:
DiscoveryEnabledNIWSServerList實現了ServerList接口,該接口用于初始化服務列表及更新服務列表。首先看一下ServerList的接口定義,其中兩個方法分別用于初始化服務列表及更新服務列表:
public interface ServerList
在DiscoveryEnabledNIWSServerList中,初始化與更新兩個方法其實調用了同一個方法來實現具體邏輯:
進入obtainServersViaDiscovery方法:
可以看到,這里先得到一個EurekaClient的實例,然后借助EurekaClient的服務發現功能,來獲取服務的實例列表。在獲取了實例信息后,判斷服務的狀態如果為UP,那么最終將它加入serverList中。
在獲取得到serverList后,會進行緩存操作。首先進入DynamicServerListLoadBalancer的setServerList方法,然后調用父類BaseLoadBalancer的setServersList方法:
在BaseLoadBalancer中,定義了兩個緩存列表:
protected volatile List
在父類的setServersList中,將拉取的serverList賦值給緩存列表allServerList:
在Ribbon從Eureka中得到了服務列表,緩存在本地List后,存在一個問題,如何保證在調用服務的時候服務仍然處于可用狀態,也就是說應該如何解決緩存列表臟讀問題?
在默認負載均衡器ZoneAwareLoadBalancer的父類BaseLoadBalancer構造方法中,調用setupPingTask方法,并在其中創建了一個定時任務,使用ping的方式判斷服務是否可用:
runPinger方法中,調用SerialPingStrategy的pingServers方法:
pingServers方法中,調用NIWSDiscoveryPing的isAlive方法:
NIWSDiscoveryPing實現了IPing接口,在IPing 接口中,僅有一個isAlive方法用來判斷服務是否可用:
public interface IPing { public boolean isAlive(Server server); }
NIWSDiscoveryPing的isAlive方法實現:
因為本地的serverList為緩存值,可能與eureka中不同,所以從eureka中去查詢該實例的狀態,如果eureka里面顯示該實例狀態為UP,就返回true,說明服務可用。
返回Pinger的runPingger的方法調用處:
在獲取到服務的狀態列表后進行循環,如果狀態改變,加入到changedServers中,并且把所有可用服務加入newUpList,最終更新upServerList中緩存值。但是在閱讀源碼中發現,創建了一個-用于監聽changedServers這一列表,但是只是一個空殼方法,并沒有實際代碼對列表變動做出實際操作。
需要注意的是,在調試過程中當我下線一個服務后,results數組并沒有按照預期的將其中一個服務的狀態返回為false,而是results數組中的元素只剩下了一個,也就說明,除了使用ping的方式去檢測服務是否在線外,Ribbon還使用了別的方式來更新服務列表。
我們在BaseLoadBalancer的setServersList方法中添加一個斷點:
等待程序運行,可以發現,在還沒有進入執行IPing的定時任務前,已經將下線服務剔除,只剩下了一個可用服務。查看調用鏈,最終可以發現使用了定時調度線程池調用了PollingServerListUpdater類的start方法,來進行更新服務操作:
回到BaseLoadBalancer的setServersList方法中:
在這里就用新的服務列表更新了舊服務列表,因此當執行IPing的線程再執行時,服務列表中只剩下了一個服務實例。
綜上可以發現,Ribbon為了解決服務列表的臟讀現象,采用了兩種手段:
更新列表
ping機制
在測試中發現,更新機制和ping機制功能基本重合,并且在ping的時候不能執行更新,在更新的時候不能運行ping,所以很難檢測到ping失敗的情況。
服務選取
服務選取的過程就是從服務列表中按照約定規則選取服務實例,與負載均衡算法相關。這里引入Ribbon對于負載均衡策略實現的接口IRule:
public interface IRule{ public Server choose(Object key); public void setLoadBalancer(ILoadBalancer lb); public ILoadBalancer getLoadBalancer(); }
其中choose為核心方法,用于實現具體的選擇邏輯。
Ribbon中,下面7個類默認實現了IRule接口,為我們提供負載均衡算法:
在剛才調試過程中,可以知道Ribbon默認使用的是ZoneAvoidanceRule區域親和負載均衡算法,優先調用一個zone區間中的服務,并使用輪詢算法,具體實現過程前面已經介紹過不再贅述。
當然,也可以由我們自己實現IRule接口,重寫其中的choose方法來實現自己的負載均衡算法,然后通過@Bean的方式注入到spring容器中。當然也可以將不同的服務應用不同的IRule策略,這里需要注意的是,Spring cloud的官方文檔中提醒我們,如果多個微服務要調用不同的IRule,那么創建出IRule的配置類不能放在ComponentScan的目錄下面,這樣所有的微服務都會使用這一個策略。
需要在主程序運行的com包外另外創建一個config包用于專門存放配置類,然后在啟動類上加上@RibbonClients注解,不同服務應用不同配置類:
@RibbonClients({@RibbonClient(name="eureka-hi",configuration = HiRuleConfig.class), @RibbonClient(name = "eureka-test",configuration = TestRuleConfig.class)}) public class ServiceFeignApplication { …… }
總結
綜上所述,在Ribbon的負載均衡中,大致可以分為以下幾步:
攔截請求,通過請求中的url地址,截取服務名稱
通過LoadBalancerClient獲取ILoadBalancer
使用Eureka獲取服務列表
通過IRule負載均衡策略選擇具體服務
ILoadBalancer通過IPing及定時更新機制來維護服務列表
重構該url地址,最終調用HttpURLConnection發起請求
了解了整個調用流程后,我們更容易明白為什么Ribbon叫做客戶端的負載均衡。與nginx服務端負載均衡不同,nginx在使用反向代理具體服務的時候,調用端不知道都有哪些服務。而Ribbon在調用之前,已經知道有哪些服務可用,直接通過本地負載均衡策略調用即可。而在實際使用過程中,也可以根據需要,結合兩種方式真正實現高可用。
最后
覺得對您有所幫助,小伙伴們可以點個贊啊,非常感謝~
公眾號『碼農參上』,一個熱愛分享的公眾號,有趣、深入、直接,與你聊聊技術。歡迎來加我好友 DrHydra9,圍觀朋友圈,做個之交。
Spring Spring Cloud 微服務 負載均衡緩存
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。