SpringCloud負載均衡源碼解析 | 帶你從表層一步步剖析Ribbon組件如何實現負載均衡功能

目錄

1、負載均衡原理

2、源碼分析

2.1、@LoadBalanced

2.2、LoadBalancerClient

2.3、RibbonAutoConfiguration

2.4、LoadBalancerAutoConfiguration

2.5、LoadBalancerIntercepor?

2.6、再回LoadBalancerClient

2.7、RibbonLoadBalancerClient

2.7.1、DynamicServerListLoadBalancer

2.7.2、getServer

2.8、BaseLoadBalancer

2.9、RoundRobinRule

2.10、總結

3、負載均衡策略

3.1、負載均衡策略

3.2、自定義負載均衡策略

3.3、饑餓加載?


🍃作者介紹:雙非本科大三網絡工程專業在讀,阿里云專家博主,專注于Java領域學習,擅長web應用開發、數據結構和算法,初步涉獵Python人工智能開發和前端開發。
🦅主頁:@逐夢蒼穹

🥡所屬專欄:微服務

📕您的一鍵三連,是我創作的最大動力🌹

1、負載均衡原理

在上一篇中,添加了@LoadBalanced注解,即可實現負載均衡功能,這是什么原理呢?

SpringCloud底層其實是利用了一個名為Ribbon的組件,來實現負載均衡功能的。

思考:發出的請求明明是http://userservice/user/2,怎么變成了http://localhost:8081/user/2的呢?

2、源碼分析

為什么我們只輸入了service名稱就可以訪問了呢?之前還要獲取ip和端口。

顯然是因為有什么組件幫我們根據service名稱,獲取到了服務實例的ip和端口。

先說結論:它就是LoadBalancerInterceptor

這個類會在對RestTemplate的請求進行攔截,然后從Eureka根據服務id獲取服務列表,隨后利用負載均衡算法得到真實的服務地址信息,替換服務id。

下面進行源碼分析。

2.1、@LoadBalanced

按住CTRL鍵,點進@LoadBalanced源碼,選擇加載完整,可以看到如下:

看起來非常的平平無奇,但是請留意上面的英文說明:

Annotation to mark a RestTemplate or WebClient bean to be configured to use a LoadBalancerClient.

簡單來說,就是這個@LoadBalanced注解是用來標記的,標記要使用的是LoadBalancerClient

所以真正起作用的其實是LoadBalancerClient這個Java類。

下面找到LoadBalancerClient這個類。

2.2、LoadBalancerClient

可以看到,這個類也是一個接口。

看一下類里面有什么方法:

只有這三個方法,三個都打斷點嘗試一下,看看攔截的是哪一個。

經過嘗試,攔截的是:

<T> T execute(String serviceId, ServiceInstance serviceInstance,LoadBalancerRequest<T> request) throws IOException;

并且,攔截到實現類是RibbonLoadBalancerClient

但是這時候,我再往下調試斷點,發現有點沒有頭緒了

后面的內容并沒有像前文這樣顯而易見的內容,都是一些判斷之類的業務邏輯。

此時想起Ribbon組件,應該就是一整套東西。

那么查一下是否還有別的類?

讓我們接著往下看

2.3、RibbonAutoConfiguration

查看ribbon包下其他的類,發現以ribbon開頭的Java文件如下:

其中有一個很引人注目的類,即是配置類RibbonAutoConfiguration

點進去看一下:

英文提示信息是:Auto configuration for Ribbon (client side load balancing).

顧名思義,這個類果然是自動配置ribbon相關內容的。

看一下這個類實現了什么方法:

此時看見了LoadBalancerClient()方法,那么這個LoadBalancerClient()LoadBalancerClient.java會不會有什么關系呢?點過去看一下:

原來這個方法是返回了一個新的RibbonLoadBalancerClient對象,并且限制了注入LoadBalancerClient對象只允許注冊一個實例。

其實,返回了一個新的RibbonLoadBalancerClient對象,本質上也是返回了一個LoadBalancerClient對象,因為RibbonLoadBalancerClient是實現LoadBalancerClient的:

2.4、LoadBalancerAutoConfiguration

進入LoadBalancerClient,查看哪些類實現了這個接口:

又出現一個自動配置類,點進去查看:

英文提示:Auto-configuration for blocking client-side load balancing.

顧名思義,自動配置阻塞客戶端負載平衡。

看來,關鍵點找到了!

看一下這個類實現了什么方法:

可以看到這里有一個LoadBalancerInterceptorConfig攔截器配置。

那么猜想里面應該是配置了攔截器

這符合之前分析的流程,Ribbon組件接收到請求,然后去找Eureka拉去實例列表,那這個過程的實現,不就是需要一個攔截器么?請接著往下看:

原來這是一個靜態內部類。這個內部類的內容如下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {@Beanpublic LoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient,LoadBalancerRequestFactory requestFactory) {return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);}@Bean@ConditionalOnMissingBeanpublic RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {return restTemplate -> {List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());list.add(loadBalancerInterceptor);restTemplate.setInterceptors(list);};}
}

這部分內容,我們讓通義靈碼解釋一下:

由此,重點就在LoadBalancerIntercepor了。

2.5、LoadBalancerIntercepor?

進入LoadBalancerIntercepor

LoadBalancerIntercepor實現了ClientHttpRequestInterceptor(翻譯過來是:客戶端Http請求攔截器)。

到這里,基本上就沒錯了,接下來就是打斷點接著攔截!

實現的方法如下:

intercept方法打斷點:

可以看到這里的intercept方法,攔截了用戶的HttpRequest請求,然后做了幾件事:

request.getURI():獲取請求uri,本例中就是 http://userservice:8081/user/2

originalUri.getHost():獲取uri路徑的主機名,其實就是服務id,userservice

this.loadBalancer.execute():處理服務id,和用戶請求。

這里的this.loadBalancerLoadBalancerClient類型:

繼續跟入末尾的execute方法。

2.6、再回LoadBalancerClient

可以注意到文章目錄其實有兩個LoadBalancerClient

第一次是斷點定位到execute方法之后沒有頭緒,

第二次則是帶著答案的合理猜測來的。

繼續跟入this.loadBalancer.execute方法:

此時來到了RibbonLoadBalancerClient.java。下面進入這個類。

2.7、RibbonLoadBalancerClient

繼續進入RibbonLoadBalancerClient的execute方法:

(進入發現其實還是RibbonLoadBalancerClient類里面的方法,只不過是別的execute方法):

2.7.1、DynamicServerListLoadBalancer

注意上圖中的DynamicServerListLoadBalancer,在這里已經拉取到實例了。

上圖是DynamicServerListLoadBalancer類的說明,意思翻譯過來就是:

具有使用動態源獲取候選服務器列表的能力的LoadBalancer。例如,服務器列表可能會在運行時更改。它還包含一些工具,其中服務器列表可以通過篩選條件傳遞,以過濾掉不符合所需條件的服務器。

2.7.2、getServer

注意到這里有一個getServer方法,點進去一探究竟:

再進chooseServer方法:

來到了ZoneAwareLoadBalancer.javachooseServer方法

再進super.chooseServer(key),來到了ZoneAwareLoadBalancer的父類BaseLoadBalancer

2.8、BaseLoadBalancer

進入BaseLoadBalancerchooseServer方法:

斷點來到了rule.choose(key)

rule則是規則的意思,查看一下rule是什么:

根據命名規范,I是指interface接口的意思,所以IRule是一個接口:

英文提示信息:

Interface that defines a "Rule" for a LoadBalancer.

A Rule can be thought of as a Strategy for loadbalacing.

Well known loadbalancing strategies include Round Robin, Response Time based etc.

信息主要內容:規則被認為是一種負載平衡策略。眾所周知的負載平衡策略包括輪詢、基于響應時間等。

所以到這一步,就已經很明顯了。

SpringCloud底層利用Ribbon實現負載均衡,而Ribbon則通過IRule實現負載均衡。

也就是說,Ribbon實現負載均衡的組件,一定是IRule的實現類

在IDEA中CTRL+H查看一下IRule的實現類:

RoundRobinRule.java就是實現了輪詢規則。

2.9、RoundRobinRule

進入RoundRobinRule,查看實現方法:

打上斷點,看看Ribbon組件有沒有使用RoundRobinRule,有則說明Ribbon的原理就是輪詢策略實現負載均衡。

我們的驗證是沒有錯的,Ribbon組件實現負載均衡就是輪詢規則。

2.10、總結

SpringCloudRibbon的底層采用了一個攔截器,攔截了RestTemplate發出的請求,對地址做了修改。用一幅圖來總結一下:

基本流程如下:

  • 攔截RestTemplate請求http://userservice/user/2
  • RibbonLoadBalancerClient會從請求url中獲取服務名稱,也就是userservice
  • DynamicServerListLoadBalancer根據userservice到eureka拉取服務列表
  • eureka返回列表,localhost:8081、localhost:8082
  • IRule利用內置負載均衡的輪詢規則,從列表中選擇一個,例如localhost:8081
  • RibbonLoadBalancerClient修改請求地址,用localhost:8081替代userservice,得到http://localhost:8081/user/2,發起真實請求

3、負載均衡策略

3.1、負載均衡策略

負載均衡的規則都定義在IRule接口中,而IRule有很多不同的實現類:

不同規則的含義如下:

內置負載均衡規則類

規則描述

RoundRobinRule

簡單輪詢服務列表來選擇服務器。它是Ribbon默認的負載均衡規則。

AvailabilityFilteringRule

對以下兩種服務器進行忽略: ?

(1)在默認情況下,這臺服務器如果3次連接失敗,這臺服務器就會被設置為“短路”狀態。短路狀態將持續30秒,如果再次連接失敗,短路的持續時間就會幾何級地增加。 ?

(2)并發數過高的服務器。如果一個服務器的并發連接數過高,配置了AvailabilityFilteringRule規則的客戶端也會將其忽略。并發連接數的上限,可以由客戶端的..ActiveConnectionsLimit屬性進行配置。

WeightedResponseTimeRule

為每一個服務器賦予一個權重值。服務器響應時間越長,這個服務器的權重就越小。這個規則會隨機選擇服務器,這個權重值會影響服務器的選擇。

ZoneAvoidanceRule

以區域可用的服務器為基礎進行服務器的選擇。使用Zone對服務器進行分類,這個Zone可以理解為一個機房、一個機架等。而后再對Zone內的多個服務做輪詢。

BestAvailableRule

忽略那些短路的服務器,并選擇并發數較低的服務器。

RandomRule

隨機選擇一個可用的服務器。

RetryRule

重試機制的選擇邏輯

默認的實現就是ZoneAvoidanceRule,是一種輪詢方案

3.2、自定義負載均衡策略

通過定義IRule實現可以修改負載均衡規則,有兩種方式:

①代碼方式:在order-service中的OrderApplication類中,定義一個新的IRule:

@Bean
public IRule randomRule(){return new RandomRule();
}

②配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改規則:

userservice: # 給某個微服務配置負載均衡規則,這里是userservice服務ribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 負載均衡規則

注意,一般用默認的負載均衡規則,不做修改。

3.3、饑餓加載?

Ribbon默認是采用懶加載,即第一次訪問時才會去創建LoadBalanceClient,請求時間會很長。

而饑餓加載則會在項目啟動時創建,降低第一次訪問的耗時,通過下面配置開啟饑餓加載:

ribbon:eager-load:enabled: trueclients: userservice

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/717369.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/717369.shtml
英文地址,請注明出處:http://en.pswp.cn/news/717369.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

OpenCV 4基礎篇| OpenCV圖像的拼接

目錄 1. Numpy (np.hstack&#xff0c;np.vstack)1.1 注意事項1.2 代碼示例 2. matplotlib2.1 注意事項2.2 代碼示例 3. 擴展示例&#xff1a;多張小圖合并成一張大圖4. 總結 1. Numpy (np.hstack&#xff0c;np.vstack) 語法結構&#xff1a; retval np.hstack(tup) # 水平…

工作日記:JavaScript fill() 方法

定義 fill() 方法用于將一個固定值替換數組的元素。 語法 array.fill(value, start, end) value&#xff1a;必填。要填充的值 start&#xff1a;可選。開始填充位置 end&#xff1a;可選。結束填充位置&#xff08;默認是數組的長度&#xff1a;array.length&#xff09;…

提取拼多多店鋪商家電話的爬蟲軟件

拼多多是中國知名的團購電商平臺&#xff0c;許多用戶在購物時都希望能夠直接聯系到店鋪商家&#xff0c;以便獲得更多的產品信息或解決問題。在這篇文章中&#xff0c;我們將介紹如何使用Python編寫一個爬蟲軟件&#xff0c;來提取拼多多店鋪商家電話。 首先&#xff0c;我們…

c++之通訊錄管理系統

1&#xff0c;系統需求 通訊錄是一個記錄親人&#xff0c;好友信息的工具 系統中需要實現的功能如下&#xff1a; 1&#xff0c;添加聯系人&#xff1a;向通訊錄中添加新人&#xff0c;信息包括&#xff08;姓名&#xff0c;性別&#xff0c;年齡&#xff0c;聯系電話&#…

構建高效的接口自動化測試框架思路

在選擇接口測試自動化框架時&#xff0c;需要根據團隊的技術棧和項目需求來綜合考慮。對于測試團隊來說&#xff0c;使用Python相關的測試框架更為便捷。無論選擇哪種框架&#xff0c;重要的是確保 框架功能完備&#xff0c;易于維護和擴展&#xff0c;提高測試效率和準確性。今…

IntelliJ IDEA 的常用快捷鍵

IntelliJ IDEA 的常用快捷鍵非常多&#xff0c;這些快捷鍵可以幫助你更高效地編寫代碼。以下是一些常用的快捷鍵總結&#xff1a; 基礎操作 CtrlN&#xff1a;查找類CtrlShiftN&#xff1a;查找文件CtrlAltL&#xff1a;格式化代碼AltInsert&#xff1a;生成代碼&#xff08;…

信息安全技術第1章——信息網絡安全基本概念

課程介紹 網絡信息安全是醫學信息工程專業的限選課。主要圍繞計算機網絡安全所涉及的主要問題進行講解&#xff0c;內容包括&#xff1a;對稱密碼與公鑰密碼的基本原理、相關算法及應用。電子郵件的安全&#xff0c;IP安全&#xff0c;Web安全&#xff0c;惡意軟件及防火墻等內…

UI自動化-(web端窗口截圖文件上傳-實操入門)

1、窗口截圖 1. UI自動化中&#xff0c;為什么需要進行窗口截圖&#xff1f; 調試和故障排除&#xff1a;截圖可以直觀地查看界面的狀態&#xff0c;快速識別和解決問題。當自動化過程中出現錯誤或異常時&#xff0c;通過查看截圖可以確定是否是界面元素的問題&#xff0c;例…

C++ opencv 學習

文章目錄 1、創建窗口2、讀取圖片3、視頻采集4、Mat的使用5、異或操作6、通道分離&#xff0c;通道合并7、色彩空間轉換8、最大值、最小值9、繪制圖像10、多邊形繪制11、隨機數12、鼠標實時繪制矩形13、歸一化14、resize操作15、旋轉翻轉16、視頻操作17、模糊操作18、高斯模糊操…

SpringBoot整合MyBatis實現增刪改查

?作者簡介:大家好,我是Leo,熱愛Java后端開發者,一個想要與大家共同進步的男人???? ??個人主頁:Leo的博客 ??當前專欄: 循序漸進學SpringBoot ?特色專欄: MySQL學習 ??本文內容: SpringBoot整合MyBatis實現增刪改查 ??個人知識庫: Leo知識庫,歡迎大家訪…

mysql之 case when

1 簡單 case 函數&#xff0c;IF函數 格式&#xff1a; CASE input_expression WHEN when_expression THENresult_expression [...n ] [ ELSEelse_result_expression ENDIF(條件,True結果,False結果)2 條件表達式 可嵌套多層&#xff0c;類似于 if … else if … else … end…

Java進階-IO(1)

進入java IO部分的學習&#xff0c;首先學習IO基礎&#xff0c;內容如下。需要了解流的概念、分類還有其他一些如集合與文件的轉換&#xff0c;字符編碼問題等&#xff0c;這次先學到字節流的讀寫數據&#xff0c;剩余下次學完。 一、IO基礎 1、背景 1.1 數據存儲問題 變量…

代碼隨想錄day11(1)字符串:反轉字符串中的單詞 (leetcode151)

題目要求&#xff1a;給定一個字符串&#xff0c;將其中單詞順序反轉&#xff0c;且每個單詞之間有且僅有一個空格。 思路&#xff1a;因為本題沒有限制空間復雜度&#xff0c;所以首先想到的是用split直接分割單詞&#xff0c;然后將單詞倒敘相加。 但如果想讓空間復雜度為O…

芯來科技發布最新NI系列內核,NI900矢量寬度可達512/1024位

參考&#xff1a;芯來科技發布最新NI系列內核&#xff0c;NI900矢量寬度可達512/1024位 (qq.com) 本土RISC-V CPU IP領軍企業——芯來科技正式發布首款針對人工智能應用的專用處理器產品線Nuclei Intelligence(NI)系列&#xff0c;以及NI系列的第一款AI專用RISC-V處理器CPU IP…

反向迭代器

反向迭代器 以list為例,我們完全可以再添加一個__list_reverse_iterator結構體,只需要修改和–的邏輯 template <class T, class Ref, class Ptr> struct __list_reverse_iterator {...self& operator(){_node _node->_prev;return *this;}self operator(int){…

Android 14.0 Launcher3定制化之桌面分頁橫線改成圓點顯示功能實現

1.前言 在14.0的系統rom產品定制化開發中&#xff0c;在進行launcher3的定制化中&#xff0c;在雙層改為單層的開發中&#xff0c;在原生的分頁 是橫線&#xff0c;而為了美觀就采用了系統原來的另外一種分頁方式&#xff0c;就是圓點比較美觀&#xff0c;接下來就來分析下相關…

網絡爬蟲部分應掌握的重要知識點

目錄 一、預備知識1、Web基本工作原理2、網絡爬蟲的Robots協議 二、爬取網頁1、請求服務器并獲取網頁2、查看服務器端響應的狀態碼3、輸出網頁內容 三、使用BeautifulSoup定位網頁元素1、首先需要導入BeautifulSoup庫2、使用find/find_all函數查找所需的標簽元素 四、獲取元素的…

基于springboot+vue的健身房管理系統

博主主頁&#xff1a;貓頭鷹源碼 博主簡介&#xff1a;Java領域優質創作者、CSDN博客專家、阿里云專家博主、公司架構師、全網粉絲5萬、專注Java技術領域和畢業設計項目實戰&#xff0c;歡迎高校老師\講師\同行交流合作 ?主要內容&#xff1a;畢業設計(Javaweb項目|小程序|Pyt…

●139.單詞拆分 ● 關于多重背包,你該了解這些! ●背包問題總結篇!

●139.單詞拆分 物品&#xff1a;wordDict里面的單詞&#xff1b;背包容量&#xff1a;s.size()。 1.dp[j]含義。dp[j]true表示字符串前j個可以拆分成字典中的單詞。dp[s.size()] 就是最后的結果&#xff0c;整個字符串能&#xff08;true&#xff09;不能&#xff08;false…

Docker 創建容器并指定時區

目錄 1. 通過環境變量設置時區&#xff08;推薦&#xff09;2. 掛載宿主機的時區文件到容器中3. 總結 要在 Docker 容器中指定時區&#xff0c;可以通過兩種方式來實現&#xff1a; 1. 通過環境變量設置時區&#xff08;推薦&#xff09; 在 Docker 運行時&#xff0c;可以通…