SpringCloud負載均衡組件Ribbon源碼分析
本文小編為大家詳細介紹“SpringCloud負載均衡組件Ribbon源碼分析”,內容詳細,步驟清晰,細節處理妥當,希望這篇“SpringCloud負載均衡組件Ribbon源碼分析”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
項目實戰
創建項目
同樣的,我們的項目現在依然有一個registry注冊中心,一個provider服務提供者,接下來,我們再次修改一下consumer服務消費者的代碼:
@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; ????} }
在這個版本,同樣的,還是創建RestTemplate Bean對象,不同的是上面僅僅增加了@LoadBalanced注解。
啟動項目驗證
依然正確返回了結果!
太神奇了吧,比我們自己開發的負載均衡組件簡單太多了吧,僅僅在restTemplate() 方法上面增加了一個@LoadBalanced注解,怎么就實現的呢?廢話不說,為了一探究竟,扒一扒源碼吧!
源碼分析
首先,點擊@LoadBalanced注解進去,沒有什么特別之處,那么我們在想想,Spring在創建Bean實例的時候,注解在什么地方起了作用?什么?不知道?翻一下這篇文章吧:
肝了兩周,一張圖解鎖Spring核心源碼
通過回顧Spring啟動以及Bean的生命周期創建過程,我們就會發現加上@LoadBalancer注解后,項目啟動時就會加載LoadBalancerAutoConfiguration這個配置類(通過spring-cloud-commons包下面的的spring.factories)。通過查看該配置類源碼,發現其有個靜態內部類LoadBalancerInterceptorConfig,其內部又創建了一個負載均衡攔截器:LoadBalancerInterceptor,該攔截器包含有一個loadBalancerClient參數:
@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); ????????????}; ????????} ????}
我們繼續點擊LoadBalancerInterceptor類進入,發現intercept方法,該方法中調用了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是一個接口,點擊進去我們發現其實現類是RibbonLoadBalancerClient,查看其繼承關系:
通過接口中的方法名稱,我們可以猜想,choose方法就是選擇其中服務列表中其中一個服務,reconstructURI方法就是重新構造請求的URI。
選擇服務
choose方法是在RibbonLoadBalancerClient實現類中實現的
????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)); ????}
在這個方法中,先調用 getServer 方法獲取服務,這個方法最終會調用 ILoadBalancer 接口的 chooseServer 方法,而 ILoadBalancer 接口的實現類默認是ZoneAwareLoadBalancer。
ZoneAwareLoadBalancer 繼承自 DynamicServerListLoadBalancer ,而在 DynamicServerListLoadBalancer 的構造方法中,調用了 this.restOfInit(clientConfig);在restOfInit這個方法中,通過 this.updateListOfServers()來獲取服務列表;
而在chooseServer ()方法中,就會根據負載均衡算法,選擇其中一個服務并返回:
?BaseLoadBalancer?zoneLoadBalancer?=?this.getLoadBalancer(zone); ?server?=?zoneLoadBalancer.chooseServer(key);
地址替換
選擇其中一個服務信息后,怎么將接口從 http://provider/hello 變為 http://localhost:8003/hello 呢?還記得上面我們說的reconstructURI方法嗎?通過配置類LoadBalancerAutoConfiguration加載后,會注入LoadBalancerInterceptor攔截器,該攔截器會攔截我們的請求,并對請求地址進行處理,重構方法的具體實現在 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); ????????????} ????????} ????}
可以看到該方法中,將原始的請求地址original,替換成了選取的服務的IP和端口。并最終調用該服務的接口方法。
讀到這里,這篇“SpringCloud負載均衡組件Ribbon源碼分析”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注蝸牛博客行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:niceseo99@gmail.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。
評論