SpringCloud超詳細(xì)講解負(fù)載均衡組件Ribbon源碼
前言
上一篇文章中我們通過自己開發(fā)了一個(gè)負(fù)載均衡組件,實(shí)現(xiàn)了隨機(jī)算法的負(fù)載均衡功能,如果要實(shí)現(xiàn)其他算法,還需要修改代碼增加相應(yīng)的功能。這一篇文章,我們將介紹一個(gè)更簡(jiǎn)單的負(fù)載均衡實(shí)現(xiàn),使用**@LoadBalanced**注解實(shí)現(xiàn)負(fù)載均衡的功能。
項(xiàng)目實(shí)戰(zhàn)
創(chuàng)建項(xiàng)目
同樣的,我們的項(xiàng)目現(xiàn)在依然有一個(gè)registry注冊(cè)中心,一個(gè)provider服務(wù)提供者,接下來,我們?cè)俅涡薷囊幌耤onsumer服務(wù)消費(fèi)者的代碼:
@EnableEurekaClient @SpringBootApplication @RestController public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } @Autowired DiscoveryClient discoveryClient; @Autowired RestTemplate restTemplate; @GetMapping("/hello2") public String hello2(String name) { String returnInfo = restTemplate.getForObject( "http://provider/hello?name={1}", String.class, name); return returnInfo; } }
在這個(gè)版本,同樣的,還是創(chuàng)建RestTemplate Bean對(duì)象,不同的是上面僅僅增加了@LoadBalanced注解。
啟動(dòng)項(xiàng)目驗(yàn)證
依然正確返回了結(jié)果!
太神奇了吧,比我們自己開發(fā)的負(fù)載均衡組件簡(jiǎn)單太多了吧,僅僅在restTemplate() 方法上面增加了一個(gè)@LoadBalanced注解,怎么就實(shí)現(xiàn)的呢?廢話不說,為了一探究竟,扒一扒源碼吧!
源碼分析
首先,點(diǎn)擊@LoadBalanced注解進(jìn)去,沒有什么特別之處,那么我們?cè)谙胂耄琒pring在創(chuàng)建Bean實(shí)例的時(shí)候,注解在什么地方起了作用?什么?不知道?翻一下這篇文章吧:
肝了兩周,一張圖解鎖Spring核心源碼
通過回顧Spring啟動(dòng)以及Bean的生命周期創(chuàng)建過程,我們就會(huì)發(fā)現(xiàn)加上@LoadBalancer注解后,項(xiàng)目啟動(dòng)時(shí)就會(huì)加載LoadBalancerAutoConfiguration這個(gè)配置類(通過spring-cloud-commons包下面的的spring.factories)。通過查看該配置類源碼,發(fā)現(xiàn)其有個(gè)靜態(tài)內(nèi)部類LoadBalancerInterceptorConfig,其內(nèi)部又創(chuàng)建了一個(gè)負(fù)載均衡攔截器:LoadBalancerInterceptor,該攔截器包含有一個(gè)loadBalancerClient參數(shù):
@ConditionalOnMissingClass({"org.springframework.retry.support.RetryTemplate"}) static class LoadBalancerInterceptorConfig { LoadBalancerInterceptorConfig() { } @Bean public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); } @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) { return (restTemplate) -> { List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; } }
我們繼續(xù)點(diǎn)擊LoadBalancerInterceptor類進(jìn)入,發(fā)現(xiàn)intercept方法,該方法中調(diào)用了LoadBalancerClient的execute方法,
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { URI originalUri = request.getURI(); String serviceName = originalUri.getHost(); Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution)); }
LoadBalancerClient是一個(gè)接口,點(diǎn)擊進(jìn)去我們發(fā)現(xiàn)其實(shí)現(xiàn)類是RibbonLoadBalancerClient,查看其繼承關(guān)系:
通過接口中的方法名稱,我們可以猜想,choose方法就是選擇其中服務(wù)列表中其中一個(gè)服務(wù),reconstructURI方法就是重新構(gòu)造請(qǐng)求的URI。
選擇服務(wù)
choose方法是在RibbonLoadBalancerClient實(shí)現(xiàn)類中實(shí)現(xiàn)的
public ServiceInstance choose(String serviceId, Object hint) { Server server = this.getServer(this.getLoadBalancer(serviceId), hint); return server == null ? null : new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server)); }
在這個(gè)方法中,先調(diào)用 getServer 方法獲取服務(wù),這個(gè)方法最終會(huì)調(diào)用 ILoadBalancer 接口的 chooseServer 方法,而 ILoadBalancer 接口的實(shí)現(xiàn)類默認(rèn)是ZoneAwareLoadBalancer。
ZoneAwareLoadBalancer 繼承自 DynamicServerListLoadBalancer ,而在 DynamicServerListLoadBalancer 的構(gòu)造方法中,調(diào)用了 this.restOfInit(clientConfig);在restOfInit這個(gè)方法中,通過 this.updateListOfServers()來獲取服務(wù)列表;
而在chooseServer ()方法中,就會(huì)根據(jù)負(fù)載均衡算法,選擇其中一個(gè)服務(wù)并返回:
BaseLoadBalancer zoneLoadBalancer = this.getLoadBalancer(zone); server = zoneLoadBalancer.chooseServer(key);
地址替換
選擇其中一個(gè)服務(wù)信息后,怎么將接口從 http://provider/hello 變?yōu)?http://localhost:8003/hello 呢?還記得上面我們說的reconstructURI方法嗎?通過配置類LoadBalancerAutoConfiguration加載后,會(huì)注入LoadBalancerInterceptor攔截器,該攔截器會(huì)攔截我們的請(qǐng)求,并對(duì)請(qǐng)求地址進(jìn)行處理,重構(gòu)方法的具體實(shí)現(xiàn)在 LoadBalancerContext 類的 reconstructURIWithServer 方法中
public URI reconstructURIWithServer(Server server, URI original) { String host = server.getHost(); int port = server.getPort(); String scheme = server.getScheme(); if (host.equals(original.getHost()) && port == original.getPort() && scheme == original.getScheme()) { return original; } else { if (scheme == null) { scheme = original.getScheme(); } if (scheme == null) { scheme = (String)this.deriveSchemeAndPortFromPartialUri(original).first(); } try { StringBuilder sb = new StringBuilder(); sb.append(scheme).append("://"); if (!Strings.isNullOrEmpty(original.getRawUserInfo())) { sb.append(original.getRawUserInfo()).append("@"); } sb.append(host); if (port >= 0) { sb.append(":").append(port); } sb.append(original.getRawPath()); if (!Strings.isNullOrEmpty(original.getRawQuery())) { sb.append("?").append(original.getRawQuery()); } if (!Strings.isNullOrEmpty(original.getRawFragment())) { sb.append("#").append(original.getRawFragment()); } URI newURI = new URI(sb.toString()); return newURI; } catch (URISyntaxException var8) { throw new RuntimeException(var8); } } }
可以看到該方法中,將原始的請(qǐng)求地址original,替換成了選取的服務(wù)的IP和端口。并最終調(diào)用該服務(wù)的接口方法。
看到這里,再想想我們上一章的內(nèi)容,是不是有異曲同工之妙?
總結(jié)
通過添加@LoadBalanced注解,就及其簡(jiǎn)單的實(shí)現(xiàn)了負(fù)載均衡的功能,與其說是Ribbon的強(qiáng)大,不如說是Spring的強(qiáng)大,Spring在整個(gè)上下文創(chuàng)建過程中,在不同的時(shí)機(jī)開放了一個(gè)又一個(gè)的接口,這就為各種組件的繼承提供了遍歷,同時(shí)也進(jìn)一步促進(jìn)了Spring生態(tài)的快速發(fā)展。
到此這篇關(guān)于SpringCloud超詳細(xì)講解負(fù)載均衡組件的文章就介紹到這了,更多相關(guān)SpringCloud負(fù)載均衡組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IntelliJ IDEA失焦自動(dòng)重啟服務(wù)的解決方法
在使用 IntelliJ IDEA運(yùn)行 SpringBoot 項(xiàng)目時(shí),你可能會(huì)遇到一個(gè)令人困擾的問題,一旦你的鼠標(biāo)指針離開當(dāng)前IDE窗口,點(diǎn)擊其他位置時(shí), IDE 窗口會(huì)失去焦點(diǎn),你的 SpringBoot 服務(wù)就會(huì)自動(dòng)重啟,所以本文給大家介紹了IntelliJ IDEA失焦自動(dòng)重啟服務(wù)的解決方法2023-10-10Java獲取當(dāng)?shù)氐娜粘鋈章鋾r(shí)間代碼分享
這篇文章主要介紹了Java獲取當(dāng)?shù)氐娜粘鋈章鋾r(shí)間代碼分享,國(guó)外猿友寫的一個(gè)類,需要的朋友可以參考下2014-06-06Spring Boot如何通過java -jar啟動(dòng)
大家開發(fā)的基于Spring Boot 的應(yīng)用 ,jar形式, 發(fā)布的時(shí)候,絕大部分都是使用java -jar 啟動(dòng)。本文主要介紹了Spring Boot如何通過java -jar啟動(dòng),一起來了解一下2021-05-05Spring?@Conditional通過條件控制bean注冊(cè)過程
這篇文章主要為大家介紹了Spring?@Conditional通過條件控制bean注冊(cè)過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02java中如何使用BufferedImage判斷圖像通道順序并轉(zhuǎn)RGB/BGR
這篇文章主要介紹了java中如何BufferedImage判斷圖像通道順序并轉(zhuǎn)RGB/BGR的相關(guān)資料,需要的朋友可以參考下2017-03-03