SpringCloud超詳細講解負載均衡組件Ribbon源碼
前言
上一篇文章中我們通過自己開發瞭一個負載均衡組件,實現瞭隨機算法的負載均衡功能,如果要實現其他算法,還需要修改代碼增加相應的功能。這一篇文章,我們將介紹一個更簡單的負載均衡實現,使用**@LoadBalanced**註解實現負載均衡的功能。
項目實戰
創建項目
同樣的,我們的項目現在依然有一個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和端口。並最終調用該服務的接口方法。
看到這裡,再想想我們上一章的內容,是不是有異曲同工之妙?
總結
通過添加@LoadBalanced註解,就及其簡單的實現瞭負載均衡的功能,與其說是Ribbon的強大,不如說是Spring的強大,Spring在整個上下文創建過程中,在不同的時機開放瞭一個又一個的接口,這就為各種組件的繼承提供瞭遍歷,同時也進一步促進瞭Spring生態的快速發展。
到此這篇關於SpringCloud超詳細講解負載均衡組件的文章就介紹到這瞭,更多相關SpringCloud負載均衡組件內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 深入理解Java SpringCloud Ribbon 負載均衡
- Java Ribbon與openfeign區別和用法講解
- SpringCloud筆記(Hoxton)Netflix之Ribbon負載均衡示例代碼
- feign調用返回object類型轉換方式
- Spring cloud alibaba之Ribbon負載均衡實現方案