目錄
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.loadBalancer是LoadBalancerClient類型:
繼續跟入末尾的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.java的chooseServer方法
再進super.chooseServer(key),來到了ZoneAwareLoadBalancer的父類BaseLoadBalancer。
2.8、BaseLoadBalancer
進入BaseLoadBalancer的chooseServer方法:
斷點來到了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