一、負載均衡是什麼web
負載均衡,根據其字面意思來講就是讓集羣服務具備共同完成工做的能力,經過負載均衡能夠在多個應用實例之間自動分配程序對外服務的能力;從而經過消除單點機器的故障,提高應用的容錯能力,讓應用更加高效、穩定、安全。算法
二、SpringCloud Ribbon是什麼spring
SpringCloud Ribbon是基於Http和TCP的一種負載工具,基於Netflix Ribbon實現;它能夠將基於服務的rest請求自動轉換成客戶端負載均衡的服務調用。安全
一、Ribbon配置負載均衡
)添加依賴ide
1 <dependency> 2 <groupId>org.springframework.cloud</groupId> 3 <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> 4 </dependency>
)從新構造RestTemplate(使得RestTemplate的請求可以實現負載均衡)工具
1 @Bean 2 @LoadBalanced 3 public RestTemplate restTemplate(){ 4 return new RestTemplate(); 5 }
)配置properties源碼分析
1 ## 局部配置-- 單獨制定客戶端(eureka-provider客戶端) 2 eureka-provider.ribbon.listOfServers=localhost:8072,localhost:8073
配置格式:<clientName>.<nameSpace>.<propertyName>=<value>ui
propertyName見com.netflix.client.config.CommonClientConfigKeythis
二、Ribbon重試機制
說到Ribbon的重試機制就須要先看看Ribbon的一些基本配置:
而後咱們看最後兩個配置,MaxAutoRetriesNextServer、MaxAutoRetries,這兩個分別是切換實例的重試次數和實例的重試次數;例如咱們有三個實例A、B、C,若是第一次請求A失敗,會繼續請求A(這即是對實例的重試),此時仍是請求失敗的話就會去重試實例B,以此類推,直到請求C失敗兩次後便算是失敗,也就是說如上圖的重試配置的話請求 2 * 3 = 6次,6次失敗就算真的失敗。
固然,若是你配置了短路器超時時間,也就是上圖的第一個配置的話,那麼你整體的重試時間加上第一次正常請求的時間也不能超時1秒。
RestTemplate封裝了多個Http客戶端,如HttpClient、OKHttp3等。
詳見:org.springframework.web.client.RestTemplate#RestTemplate(org.springframework.http.client.ClientHttpRequestFactory)
一、構造本身的RestTemplate
1 @Bean 2 @LoadBalanced 3 public RestTemplate restTemplate(){ 4 return new RestTemplate(new OkHttp3ClientHttpRequestFactory(okHttpClient())); 5 } 6 7 @Bean 8 public OkHttpClient okHttpClient() { 9 OkHttpClient.Builder builder = new OkHttpClient.Builder(); 10 builder.connectTimeout(30, TimeUnit.SECONDS) 11 .readTimeout(10, TimeUnit.SECONDS) 12 .writeTimeout(10,TimeUnit.SECONDS) 13 .retryOnConnectionFailure(true); 14 return builder.build(); 15 }
咱們知道要想是使客戶端的請求可以負載均衡的話,只須要從新構造RestTemplate,併爲其加上@LoadBalanced註解便可,那爲何加上這個註解就好了呢?
一、首先咱們要知道@LoanBalanced是對RestTemplate加強,而RestTemplate是對Http請求的一個封裝,因此咱們猜想加強時使用的應該是攔截器
)咱們進入RestTemplate,並找到父類org.springframework.http.client.support.InterceptingHttpAccessor發現其有一個屬性private List<ClientHttpRequestInterceptor> interceptors,這即是spring對http請求的封裝,interceptors即是請求所須要通過的攔截器集合;
1 private List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>(); 2 3 /** 4 * Sets the request interceptors that this accessor should use. 5 */ 6 public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) { 7 this.interceptors = interceptors; 8 } 9 10 /** 11 * Return the request interceptor that this accessor uses. 12 */ 13 public List<ClientHttpRequestInterceptor> getInterceptors() { 14 return interceptors; 15 }
)而後咱們看看這個攔截器集合在哪set的 >>> public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors)
根據類名咱們猜想不是上圖中的第2個,就是第3個,而後咱們分別查看,得知是第2個,咱們如今看看第2個的實現:
1 @Configuration 2 @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") 3 static class LoadBalancerInterceptorConfig { 4 @Bean 5 public LoadBalancerInterceptor ribbonInterceptor( 6 LoadBalancerClient loadBalancerClient, 7 LoadBalancerRequestFactory requestFactory) { 8 return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); 9 } 10 11 @Bean 12 @ConditionalOnMissingBean 13 public RestTemplateCustomizer restTemplateCustomizer( 14 final LoadBalancerInterceptor loadBalancerInterceptor) { 15 return new RestTemplateCustomizer() { 16 @Override 17 public void customize(RestTemplate restTemplate) { 18 List<ClientHttpRequestInterceptor> list = new ArrayList<>( 19 restTemplate.getInterceptors()); 20 list.add(loadBalancerInterceptor); 21 restTemplate.setInterceptors(list); 22 } 23 }; 24 } 25 }
從18到21行代碼咱們能夠看出其在原來http攔截器集合的基礎上又增長了loadBalancerInterceptor這個攔截器,而這個攔截器正好就是第5行的那個bean。
根據上述結論咱們能夠得知:RestTemplate加上了@LoadBalanced註解後,其實就是爲http加強了一個攔截器,也就是org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor
)那麼爲何添加了攔截器會生效呢,咱們能夠根據RestTemplate的一個請求debug(請求有點深)
如:String result = restTemplate.getForObject("http://eureka-provider/updateProduct/" + productName + "/" + num, String.class);
通過debug後咱們能夠找到這樣一個方法 >>> org.springframework.http.client.InterceptingClientHttpRequest.InterceptingRequestExecution#execute
1 @Override 2 public ClientHttpResponse execute(HttpRequest request, final byte[] body) throws IOException { 3 if (this.iterator.hasNext()) { 4 // 拿到當前攔截器集 5 ClientHttpRequestInterceptor nextInterceptor = this.iterator.next(); 6 // 執行當前攔截器的intercept方法,添加的LoanBalancerInterceptor攔截器就是這樣執行的 7 return nextInterceptor.intercept(request, body, this); 8 } else { 9 ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod()); 10 for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) { 11 List<String> values = entry.getValue(); 12 for (String value : values) { 13 delegate.getHeaders().add(entry.getKey(), value); 14 } 15 } 16 if (body.length > 0) { 17 if (delegate instanceof StreamingHttpOutputMessage) { 18 StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate; 19 streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() { 20 @Override 21 public void writeTo(final OutputStream outputStream) throws IOException { 22 StreamUtils.copy(body, outputStream); 23 } 24 }); 25 } else { 26 StreamUtils.copy(body, delegate.getBody()); 27 } 28 } 29 return delegate.execute(); 30 } 31 }
二、而後咱們來看看LoanBalancerInterceptor的實現
1 public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor { 2 3 private LoadBalancerClient loadBalancer; 4 private LoadBalancerRequestFactory requestFactory; 5 6 public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) { 7 this.loadBalancer = loadBalancer; 8 this.requestFactory = requestFactory; 9 } 10 11 public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) { 12 // for backwards compatibility 13 this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer)); 14 } 15 16 @Override 17 public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, 18 final ClientHttpRequestExecution execution) throws IOException { 19 final URI originalUri = request.getURI(); 20 String serviceName = originalUri.getHost(); 21 Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); 22 return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution)); 23 } 24 }
)從LoanBalancerInterceptor的攔截方法intercept中能夠看出execute執行的是loanBanlancer的execute
)這也驗證了@LoadBalanced註解上的那句Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient(將RestTemplate bean標記爲使用LoadBalancerClient的註解)
)而後咱們來看看execute的實現 >>> public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException
1 @Override 2 public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { 3 // 拿到可用的服務列表 4 ILoadBalancer loadBalancer = getLoadBalancer(serviceId); 5 // 根據負載均衡算法,從能夠的服務列表中選出一個服務 6 Server server = getServer(loadBalancer); 7 if (server == null) { 8 throw new IllegalStateException("No instances available for " + serviceId); 9 } 10 RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, isSecure(server, 11 serviceId), serverIntrospector(serviceId).getMetadata(server)); 12 // 獲得服務後,最終執行請求 13 return execute(serviceId, ribbonServer, request); 14 }
獲取可用服務列表、負載均衡算法這裏就不講解了,有興趣的同窗能夠本身去看看 (*^▽^*)