spring cloud ribbon 是一個基於 HTTP 和 TCP 的客戶端負載均衡工具,它基於Netflix Ribbon 實現。經過Spring Cloud 的封裝,能夠輕鬆的將面向服務的REST模塊請求自動轉換爲客戶端負載均衡的服務調用。Spring Cloud Ribbon 雖然只是一個工具類框架,不像服務註冊中心、配置中心、API網關那樣須要獨立部署,但它幾乎存在於每個Spring Cloud 構建的微服務和基礎設施中。由於微服務間的調用,API網關的請求轉發等內容,實際上都是經過 Ribbon 來實現的,包括後續要介紹的 Feign,它也是基於Ribbon實現的工具。java
負載均衡在系統架構中是一個很是重要,而且不得不去實施的內容。由於負載均衡是對系統的高可用、網絡壓力的緩解和處理能力擴容的重要手段之一。一般說的負載均衡都指的是服務端負載均衡,其中分爲硬件負載均衡和軟件負載均衡。硬件負載均衡主要是經過在服務器節點之間安裝專門用於負載均衡的設備,好比F5等;而軟件負載均衡則是經過在服務器上安裝一些具備負載均衡功能或模塊的軟件來完成請求分發工做,好比Nginx等。不論是硬件仍是軟件負載均衡,只要是服務器端負載均衡都能以相似下圖的架構方式構建起來:web
硬件負載均衡的設備或是軟件負載均衡的軟件模塊都會維護一個下掛可用的服務端清單,經過心跳監測來剔除故障的服務端節點以保證清單中都是能夠正常訪問的服務端節點。當客戶端發送請求到負載均衡設備的時候,該設備按某種算法(好比線性輪詢、按權重負載、按流量負載等)從維護的可用服務端清單中取出一臺服務端的地址,而後進行轉發。算法
而客戶端的負載均衡和服務端的負載均衡最大的不一樣點在於上面所提到的服務清單所存儲的位置。在客戶端負載均衡中,全部客戶端節點都維護着本身要訪問的服務端清單,而這些服務端的清單來自於服務註冊中心,好比上一章中介紹的Eureka服務端。同服務端負載均衡的架構相似,在客戶端負載均衡中也須要心跳去維護服務端清單的健康性,只是這個步驟須要與服務註冊中心配合完成。在Spring Cloud 實現的服務治理框架中,默認會建立針對各個服務治理框架的 Ribbon 自動化整合配置,好比 Eureka 中的 org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,Consul 中的 org.springframework.cloud.consul.discovery.RibbonConsulAutoConfiguration。在實際使用的時候,能夠經過查看這兩個類的實現,以找到它們的配置詳情來幫助咱們更好的使用它。spring
經過 Spring Cloud Ribbon 的封裝,咱們在微服務架構中使用客戶端負載均衡調用很是簡單,只須要以下兩步:數組
上一章中,咱們經過引入Ribbon實現了服務消費者的客戶端負載均衡功能。其中,咱們使用了一個很是有用的對象 RestTemplate 。該對象會使用 Ribbon 的自動化配置,同時經過配置 @LoadBalanced 還可以開啓客戶端負載均衡。以前演示了經過 RestTemplate 實現了最簡單的服務訪問,下面詳細介紹 RestTemplate 針對幾種不一樣請求類型和參數類型的服務調用實現。服務器
在 RestTemplate 中,對GET請求能夠經過以下兩個方法進行調用。網絡
第一種:getForEntity 函數。該方法返回的是 ResponseEntity,該對象是Spring 對 HTTP 請求響應的封裝,其中主要存儲了 HTTP 的幾個重要元素,好比 HTTP 請求狀態碼的枚舉對象 HttpStatus(404,500這些錯誤碼)、在它的父類 HttpEntity 中還存儲着 HTTP 請求的頭信息對象 HttpHeaders 以及泛型類型的請求體對象。好比下面的例子,就是訪問HELLO-SERVER服務的/index請求,同時最後一個參數didi 會替換 url 中的 {1} 佔位符,而返回的 ResponseEntity 對象中的 body 內容類型會根據第二個參數轉換爲 String 類型。架構
RestTemplate restTemplate = new RestTemplate (); ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/index?name={1}",String.class,"didi"); String body = responseEntity.getBody();
若但願返回一個自定義類型,好比返回User類型,能夠把第二個參數換成User.class,以下:併發
RestTemplate restTemplate = new RestTemplate (); ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/index?name={1}",User.class,"didi"); User body = responseEntity.getBody();
上面的例子是比較經常使用的方法,getForEntity 函數實際上提供了一下三種不一樣的重載實現。app
第二種:getForObject 函數。該方法能夠理解爲對 getForEntity 的進一步封裝,它經過 HttpMessageConverterExtractor 對 HTTP 的請求響應體 body 內容進行對象轉換,實現請求直接返回包裝好的對象內容。好比:
RestTemplate restTemplate = new RestTemplate (); String body = restTemplate .getForObject(url, String.class);
當body 是一個User對象時,能夠直接這樣實現:
RestTemplate restTemplate = new RestTemplate (); User body = restTemplate .getForObject(url, User.class);
該方法也提供了三種不一樣的重載實現(參數和上面的方法相似):
在RestTemplate中,對POST請求時能夠經過以下三個方法進行調用實現。
第一種:postForEntity 函數。使用方法和 getForEntity 函數相似
postForEntity 函數也實現了三種不一樣的重載方法。
這幾個函數的參數用法大部分都與 getForEntity 函數一致。
第二種:postForObject 函數。該函數也和 getForObject 函數相似,它的做用是簡化 postForEntity 函數的後續處理。
postForObject 函數也實現了三種不一樣的重載方法(使用方法和上述相似):
第三種:postForLocation 函數
postForLocation 也實現了三種不一樣的重載方法(使用方法和上述相似):
這兩個請求的函數都直接以 put 、delete 做爲方法名,使用方法和上述相似。
從上述消費者示例中能夠發現, @LoadBalanced 註解實現了客戶端的負載均衡。經過搜索LoadBalancerClient 能夠發現,這是 Spring Cloud 中定義的一個接口:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.cloud.client.loadbalancer; import java.io.IOException; import java.net.URI; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest; import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser; public interface LoadBalancerClient extends ServiceInstanceChooser { <T> T execute(String var1, LoadBalancerRequest<T> var2) throws IOException; <T> T execute(String var1, ServiceInstance var2, LoadBalancerRequest<T> var3) throws IOException; URI reconstructURI(ServiceInstance var1, URI var2); }
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.cloud.client.loadbalancer; import org.springframework.cloud.client.ServiceInstance; public interface ServiceInstanceChooser { ServiceInstance choose(String var1); }
從該接口中,咱們能夠經過定義的抽象方法來了解客戶端負載均衡器中應具有的幾種能力:
順着 LoadBalancerClient 接口的所屬包 org.springframework.cloud.client.loadbalancer,咱們對其內容進行整理,能夠得出以下圖所示的關係:
從類名可初步判斷 loadBalancerAutoConfiguration 爲實現客戶端負載均衡器的自動化配置類。經過查看源碼,能夠獲得驗證:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.cloud.client.loadbalancer; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestTransformer; import org.springframework.cloud.client.loadbalancer.LoadBalancerRetryProperties; import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer; import org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory.NeverRetryFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.retry.support.RetryTemplate; import org.springframework.web.client.RestTemplate; @Configuration @ConditionalOnClass({RestTemplate.class}) @ConditionalOnBean({LoadBalancerClient.class}) @EnableConfigurationProperties({LoadBalancerRetryProperties.class}) public class LoadBalancerAutoConfiguration { @LoadBalanced @Autowired( required = false ) private List<RestTemplate> restTemplates = Collections.emptyList(); @Autowired( required = false ) private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList(); public LoadBalancerAutoConfiguration() { } @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializer(final List<RestTemplateCustomizer> customizers) { return new SmartInitializingSingleton() { public void afterSingletonsInstantiated() { Iterator var1 = LoadBalancerAutoConfiguration.this.restTemplates.iterator(); while(var1.hasNext()) { RestTemplate restTemplate = (RestTemplate)var1.next(); Iterator var3 = customizers.iterator(); while(var3.hasNext()) { RestTemplateCustomizer customizer = (RestTemplateCustomizer)var3.next(); customizer.customize(restTemplate); } } } }; } @Bean @ConditionalOnMissingBean public LoadBalancerRequestFactory loadBalancerRequestFactory(LoadBalancerClient loadBalancerClient) { return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers); } @Configuration @ConditionalOnClass({RetryTemplate.class}) public static class RetryInterceptorAutoConfiguration { public RetryInterceptorAutoConfiguration() { } @Bean @ConditionalOnMissingBean public RetryLoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties, LoadBalancedRetryPolicyFactory lbRetryPolicyFactory, LoadBalancerRequestFactory requestFactory) { return new RetryLoadBalancerInterceptor(loadBalancerClient, properties, lbRetryPolicyFactory, requestFactory); } @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer(final RetryLoadBalancerInterceptor loadBalancerInterceptor) { return new RestTemplateCustomizer() { public void customize(RestTemplate restTemplate) { ArrayList list = new ArrayList(restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); } }; } } @Configuration @ConditionalOnClass({RetryTemplate.class}) public static class RetryAutoConfiguration { public RetryAutoConfiguration() { } @Bean public RetryTemplate retryTemplate() { RetryTemplate template = new RetryTemplate(); template.setThrowLastExceptionOnExhausted(true); return template; } @Bean @ConditionalOnMissingBean public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() { return new NeverRetryFactory(); } } @Configuration @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 new RestTemplateCustomizer() { public void customize(RestTemplate restTemplate) { ArrayList list = new ArrayList(restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); } }; } } }
從該類的類註解可知,Ribbon 實現的負載均衡自動化配置須要知足下面兩個條件。
在該類中,主要作了下面三件事:
接着看下 LoadBalancerInterceptor 攔截器是如何講一個普通的 RestTemplate 變成客戶端負載均衡的:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.cloud.client.loadbalancer; import java.io.IOException; import java.net.URI; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.util.Assert; public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor { private LoadBalancerClient loadBalancer; private LoadBalancerRequestFactory requestFactory; public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) { this.loadBalancer = loadBalancer; this.requestFactory = requestFactory; } public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) { this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer)); } public ClientHttpResponse intercept(HttpRequest request, byte[] body, 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 的實現。當一個被 @LoadBalanced 註解修飾的 RestTemplate 對象向外發起 HTTP 請求時,會被 LoadBalancerInterceptor 類的 intercept 函數所攔截。因爲咱們在使用 RestTemplate 時採用了服務名做爲host,因此直接從 HttpRequest 的 URI 對象中經過 getHost 函數就能夠拿到服務名,而後調用 execute 函數去根據服務名來選擇實例併發起實際的請求。
到此,LoadBalancerClient 還只是一個抽象的負載均衡器接口,因此咱們還須要找到它的具體實現類來進一步分析。經過查看 Ribbon 的源碼,能夠在 org.springframework.cloud.netflix.ribbon 包下找到對應的實現類 RibbonLoadBalancerClient 。
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId); Server server = this.getServer(loadBalancer); if(server == null) { throw new IllegalStateException("No instances available for " + serviceId); } else { RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server)); return this.execute(serviceId, ribbonServer, request); } } public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException { Server server = null; if(serviceInstance instanceof RibbonLoadBalancerClient.RibbonServer) { server = ((RibbonLoadBalancerClient.RibbonServer)serviceInstance).getServer(); } if(server == null) { throw new IllegalStateException("No instances available for " + serviceId); } else { RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId); RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server); try { Object ex = request.apply(serviceInstance); statsRecorder.recordStats(ex); return ex; } catch (IOException var8) { statsRecorder.recordStats(var8); throw var8; } catch (Exception var9) { statsRecorder.recordStats(var9); ReflectionUtils.rethrowRuntimeException(var9); return null; } } }
能夠看到,在execute 函數的實現中,第一步作的就是經過 getServer 根據傳入的服務名 serviceId 去得到具體的服務實例:
protected Server getServer(ILoadBalancer loadBalancer) { return loadBalancer == null?null:loadBalancer.chooseServer("default"); }
經過該函數的實現,能夠看到這裏獲取具體服務實例的時候並無使用 LoadBalancerClient 接口中的 choose 函數,而是使用了 Netflix Ribbon 自身的 ILoadBalancer 接口中定義的 chooseServer 函數。
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.netflix.loadbalancer; import com.netflix.loadbalancer.Server; import java.util.List; public interface ILoadBalancer { void addServers(List<Server> var1); Server chooseServer(Object var1); void markServerDown(Server var1); /** @deprecated */ @Deprecated List<Server> getServerList(boolean var1); List<Server> getReachableServers(); List<Server> getAllServers(); }
能夠看到,在該接口中定義了一個客戶端負載均衡器須要的一系列抽象操做。
在該接口定義中涉及的Server 對象定義是一個傳統的服務端節點,在該類中存儲了服務端節點的一些元數據信息,包括 host、port 以及一些部署信息等。
對於該接口的實現,能夠整理出下圖所示的結構。能夠看到 BaseLoadBalancer 類實現了基礎的 負載均衡,而 DynamicServerListLoadBalancer 和 ZoneAwareLoadBalancer 在負載均衡的策略上作了一些功能的擴展。
那麼在整合Ribbon的時候 Spring Cloud 默認採用了哪一個具體實現呢,能夠經過 RibbonClientConfiguration 配置類看出在整合時默認採用了 ZoneAwareLoadBalancer 來實現負載均衡器。
@Bean @ConditionalOnMissingBean public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) { return (ILoadBalancer)(this.propertiesFactory.isSet(ILoadBalancer.class, this.name)?(ILoadBalancer)this.propertiesFactory.get(ILoadBalancer.class, config, this.name):new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater)); }
下面回到 RibbonLoadBalancerClient 的 execute 函數邏輯,在經過 ZoneAwareLoadBalancer 的 chooseServer 函數獲取負載均衡策略分配到的服務實例對象 Server 後,將其內容包裝成 RibbonServer 對象,而後使用該對象再回調 LoadBalancerInterceptor 請求攔截器中 LoadBalancerRequest 的 apply(final ServiceInstance instance)函數,向一個實際的具體服務實例發起請求,從而實現一開始以服務名爲host 的 URI 請求到host:port形式的實際訪問地址的轉換。
…………