前言
微服務間相互調用的基礎上,服務間的調用更多是以調用某多實例服務下的某個實例的形式。而這就需要用到負載均衡技術。對于開發(fā)者而言,只要通過@LoadBalance注解就開啟了負載均衡。如此簡單的操作底層究竟是什么樣的,我想你也很想知道。
1.調用形式
在《SpringCloud集成Eureka并實現(xiàn)負載均衡》的基礎之上,我們可以進行一個小小的實驗,debug運行程序,通過postman發(fā)起一個請求,A服務會去遠程調用B服務,debug發(fā)現(xiàn)發(fā)送的url為:http://user-service/user/1,毫無疑問的是,這就是A調用B的途徑
同樣地,拿到這個url我們去postman里發(fā)送請求:
發(fā)現(xiàn)請求無法發(fā)送出去,路徑出了問題。觀察路徑中的參數(shù)user-service發(fā)現(xiàn)他是B服務的服務名稱,那為什么在A服務里向B服務發(fā)送“服務名稱-接口路徑-參數(shù)”形式的請求就能夠正常響應?
結合集成負載均衡的過程,這一定是Ribbon在發(fā)揮作用
2.LoadBalancerInterceptor
負載均衡的前提不是傳遞一個具體的url,肯定是Ribbon做了某種解析,通過服務名稱得到了服務下的實例列表,從而拉取Eureka-Server中的服務注冊表來將請求映射到指定的某個實例上。
結合曾經前后端分離的web開發(fā)經驗,后端經常會在攔截器中攔截前端發(fā)來的請求來對請求做一些操作,比如校驗、拼接、鑒權…調用方發(fā)送請求和接收方收到的請求并不一致,這其中會不會也是有一個類似于攔截器的東西攔截了請求,并且轉換了請求呢?
答案是必然的,那是誰——LoadBalancerInterceptor
可以看到的是,他實現(xiàn)了ClientHttpRequestInterceptor接口,具體用法細節(jié)直接去看接口中聲明的方法
直觀的看出接口中聲明了一個intercept()方法并且接受了HttpRequest參數(shù)來攔截了客戶端的http請求,并且修改的請求體!這么一看URL更改的謎底就在此處揭曉了,那么方法底層具體是怎么實現(xiàn)的呢:
3.負載均衡流程分析
3.1 調用流程圖
Debug源碼之前先來看一下源碼中的調用鏈路總體流程圖(手圖):
概括來看則是:攔截請求—讀取服務—拉取服務列表—選擇規(guī)則—返回服務實例
3.2 intercept()方法
下面我們開始Debug:
1.當發(fā)送請求使得服務間發(fā)生調用關系,調用請求會先傳遞到攔截器中的intercept方法,可以看到的是目前還和發(fā)送是保持一致
2.繼續(xù)向下執(zhí)行,開始解析請求,拿到了請求中的URI——通過getHost()方法拿到了主機地址(服務的名稱)
3.3 execute()方法
3.Ribbon開始做負載均衡處理
4.兩次步入之后進入到execute()方法內部,發(fā)現(xiàn)傳遞進來的服務名稱作為服務Id進入到了getLoadBalance()方法,并且得到了一個ILoadbalance接口對象,而在該對象中封裝了很多的信息:
這里記住服務實例id的值:host.docker.internal:8084,這就是Eureka客戶端接收到的實例信息
3.4 getServer()方法
5.接口對象作為參數(shù)傳遞到了getServer()方法,得到了一個server對象進入到方法內部。發(fā)現(xiàn)與此同時傳遞了一個Object類型的對象用于指定服務器的規(guī)則或條件,不過到目前為止,這個參數(shù)一直都是null作為傳遞,即loadBalancer.chooseServer()方法采用的是‘default’的方式進行選擇
3.4 子類的chooseServer()方法
6.再次步入到chooseServer()方法,發(fā)現(xiàn)是在一個名為BaseLoadBalancer類(這個類是負載均衡器的具體實現(xiàn)后面會具體分析)下重寫的父類方法
此時:可以判斷的是getLoadBalancerStats().getAvailableZones().size() <= 1為TRUE
3.5 getLoadBalancerStats().getAvailableZones().size() <= 1
對于表達式:getLoadBalancerStats().getAvailableZones().size() <= 1進行分析
發(fā)現(xiàn)在BaseLoadBalancer類中通過繼承抽象類AbstractLoadBalancer并重寫getLoadBalancerStats()抽象方法,獲取到了一個loadbalancer統(tǒng)計信息集合LoadBalancerStats
而封裝在LoadBalancerStats中的信息里有一個ConcurrentHashMap類型的集合屬性,即
volatile Map<String, List<? extends Server>> upServerListZoneMap = new ConcurrentHashMap<String, List<? extends Server>>();
用于存儲可用的服務列表,這個集合中的每個條目都代表一個區(qū)域,鍵是區(qū)域名稱,值是該區(qū)域下可用服務器的列表。
后續(xù)的.getAvailableZones()方法則是獲取這一屬性值中所有的鍵,也就是可用的服務區(qū)域,并作為Set集合返回來進行判斷
很顯然,這里進一步論證getLoadBalancerStats().getAvailableZones().size() <= 1是為true的,后續(xù)就會去調用父類的chooseServer()方法
3.6 父類的chooseServer()方法
7.步入到父類的chooseServer()方法中,發(fā)現(xiàn)最后返回了一個Server類型的對象,這肯定就是具體的服務實例信息了。
3.7 IRule接口下的實例
去追蹤rule變量,發(fā)現(xiàn)是一個IRule接口的實例,即為負載均衡提供規(guī)則的接口
并且此接口下有大量的規(guī)則實現(xiàn),而默認的規(guī)則方式則為輪詢調度:
可是看到上圖在debug時,rule變量右側灰色顯示的是rule:RandomRule@12045
這是因為我通過配置IRule類型的Bean指定了負載均衡的規(guī)則:
@Bean
public IRule randomRule() {
return new RandomRule();
}
只要把他注釋掉,程序就會繼續(xù)去采用默認的規(guī)則即RoundRobinRule
3.8 最終的choose()方法—return server
8.了解了IRule接口的rule實例,再去看他最終調用的choose()方法。同樣地步入進去,由于是默認規(guī)則,則按照流程進入到了RoundRobinRule規(guī)則實現(xiàn)中的choose方法(其實IRule接口下的每一個規(guī)則實現(xiàn)類都有choose方法)
實現(xiàn)了ILoadBalancer
接口的負載均衡器對象作為參數(shù)傳遞到了方法中,與此同時key為default。開始為隨機選擇預熱。
3.9 choose()方法內部分析
9.進入到while循環(huán)中,不斷選擇服務器,直到找到一個可用的服務器。隨后會判斷線程是否中斷,如果中斷了,則直接返回null。
這樣的情況出現(xiàn)頻率還是很高,由此可見,這個小設計會減少很多不必要計算,提升了程序運行的效率。
而后這是分別獲取兩個服務列表
從列表中選擇一個
兜底操作,對選擇的做判斷
最后成功返回Server實例給chooseServer()方法,服務發(fā)起者發(fā)送http://user-service/user請求通過Ribbon最后輪詢到了localhost:8084服務實例上文章來源:http://www.zghlxwxcb.cn/news/detail-751458.html
4. 彩蛋
現(xiàn)在有很多公司都在用Nacos替換Eureka,因為感知服務列表的變化不夠敏感,感知下線服務太過遲鈍,就像下面這種情況:
服務實例已經下線,時間大約過了一分鐘,卻還是把下線的服務加載到了可用服務列表里(upList),其實這并不怪Ribbon,都是Eureka的錯
針對這種情況我們留個彩蛋,下次再來talk about~文章來源地址http://www.zghlxwxcb.cn/news/detail-751458.html
到了這里,關于【SpringCloud】Eureka基于Ribbon負載均衡的調用鏈路流程分析的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!