SpringCloud学习笔记(四、SpringCloud Netflix Ribbon)

目录:

  • Ribbon简介
  • Ribbon的应用
  • RestTemplate简介
  • Ribbon负载均衡源码分析

Ribbon简介:

1、负载均衡是什么

负载均衡,根据其字面意思来说就是让集群服务具有共同完成工作的能力,通过负载均衡可以在多个应用实例之间自动分配程序对外服务的能力;从而通过消除单点机器的故障,提升应用的容错能力,让应用更加高效、稳定、安全。

SpringCloud学习笔记(四、SpringCloud Netflix Ribbon)

2、SpringCloud Ribbon是什么

SpringCloud Ribbon是基于Http和TCP的一种负载工具,基于Netflix Ribbon实现;它可以将基于服务的rest请求自动转换成客户端负载均衡的服务调用。

Ribbon的应用:

1、Ribbon配置

)添加依赖

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>

propertyName见com.netflix.client.config.CommonClientConfigKey

2、Ribbon重试机制

说到Ribbon的重试机制就需要先看看Ribbon的一些基本配置:

SpringCloud学习笔记(四、SpringCloud Netflix Ribbon)

然后我们看最后两个配置,MaxAutoRetriesNextServer、MaxAutoRetries,这两个分别是切换实例的重试次数和实例的重试次数;例如我们有三个实例A、B、C,如果第一次请求A失败,会继续请求A(这便是对实例的重试),此时还是请求失败的话就会去重试实例B,以此类推,直到请求C失败两次后便算是失败,也就是说如上图的重试配置的话请求 2 * 3 = 6次,6次失败就算真的失败。

当然,如果你配置了短路器超时时间,也就是上图的第一个配置的话,那么你总体的重试时间加上第一次正常请求的时间也不能超时1秒。

RestTemplate简介:

RestTemplate封装了多个Http客户端,如HttpClient、OKHttp3等。

详见:org.springframework.web.client.RestTemplate#RestTemplate(org.springframework.http.client.ClientHttpRequestFactory)

1、构造自己的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 }

Ribbon负载均衡源码分析:

我们知道要想是使客户端的请求能够负载均衡的话,只需要重新构造RestTemplate,并为其加上@LoadBalanced注解即可,那为什么加上这个注解就行了呢?

1、首先我们要知道@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)

SpringCloud学习笔记(四、SpringCloud Netflix Ribbon)

根据类名我们猜测不是上图中的第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 }

2、然后我们来看看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 }

获取可用服务列表、负载均衡算法这里就不讲解了,有兴趣的同学可以自己去看看 (*^▽^*)

上一篇:关于Unity中FPS第一人称射击类游戏制作(专题十)


下一篇:Java Spring resttemplate字符编码