Spring Cloud微服务技术栈(五):客户端负载均衡Spring Cloud Ribbon部分源码分析

释放双眼,带上耳机,听听看~!

在第二节《Spring Cloud微服务技术栈(二):搭建高可用Eureka Server、服务注册与发现》,我们搭建服务消费者的时候,使用到了客户端负载均衡,那时候只是在创建RestTemplate对象的代码上加上了@LoadBalanced注解,这么简单的一个步骤就实现了客户端负载均衡的功能,那么它是如何实现的呢?本篇文章将从基础源码出发,来探讨一下客户端负载均衡的原理。

源码分析

为了使客户端具备负载均衡的能力,我们在代码中将RestTemplate交给Spring管理的时候,会加上@LoadBalanced注解,如下代码所示:


1
2
3
4
5
6
7
1@Bean
2@LoadBalanced
3public RestTemplate restTemplate() {
4    return new RestTemplate();
5}
6
7

加上这个注解以后,那么RestTemplate实例对象就具备了负载均衡的能力,为什么加上这个注解以后,RestTemplate实例对象就具备了负载均衡的能力了呢?我们进入到该注解的源码中一探究竟。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1package org.springframework.cloud.client.loadbalancer;
2
3import org.springframework.beans.factory.annotation.Qualifier;
4
5import java.lang.annotation.Documented;
6import java.lang.annotation.ElementType;
7import java.lang.annotation.Inherited;
8import java.lang.annotation.Retention;
9import java.lang.annotation.RetentionPolicy;
10import java.lang.annotation.Target;
11
12/**
13 * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
14 * @author Spencer Gibb
15 */
16@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
17@Retention(RetentionPolicy.RUNTIME)
18@Documented
19@Inherited
20@Qualifier
21public @interface LoadBalanced {
22}
23
24

从该注解的代码中看来,它和普通的注解并没有太大的区别,我们从它的注释中可以了解到:“该注解用来给RestTemplate做标记,以使用负载均衡的客户端(LoadBalancerClient)来配置它”。从注释中,我们捕获一个重要的关键词,那就是负载均衡的客户端——LoadBalancerClient,我们在源码中搜索LoadBalancerClient,发现它是Spring Cloud中定义的一个接口:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1package org.springframework.cloud.client.loadbalancer;
2
3import org.springframework.cloud.client.ServiceInstance;
4
5import java.io.IOException;
6import java.net.URI;
7
8public interface LoadBalancerClient extends ServiceInstanceChooser {
9
10  <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
11
12  <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
13 
14  URI reconstructURI(ServiceInstance instance, URI original);
15}
16
17

它继承了ServiceInstanceChooser接口,该接口中有一个抽象方法choose,代码如下:


1
2
3
4
5
6
7
8
9
10
1package org.springframework.cloud.client.loadbalancer;
2
3import org.springframework.cloud.client.ServiceInstance;
4
5public interface ServiceInstanceChooser {
6
7    ServiceInstance choose(String serviceId);
8}
9
10

从两个接口中我们可以初步判定负载均衡器应该具备四个基本方法,对于四个基本方法,我们来分别了解一下:

  • choose方法:它是根据传入的服务ID,从负载均衡器中找出对应的服务实例。
  • execute方法(第一个):使用从负载均衡器中根据服务ID选择出的服务实例来执行请求内容。
  • execute方法(第二个):使用指定的服务实例来执行请求内容。
  • reconstructURI方法:根据传入的服务实例和原始的请求URI来构建一个合适的host:port形式的URI。在分布式服务调用中,我们使用的是服务名代替host:port形式来发起服务调用请求的,如:String result = restTemplate.getForEntity("http://producer-service/hello/producer", String.class).getBody();,这里使用的是服务提供方的服务名producer-service来进行调用的,那么这个方法就是在运行时将服务名称形式的调用替换为host:port形式。

我们一直在强调服务实例,难道服务实例对象存储在服务调用方这边?显然不是,我们服务调用方从Eureka Server拉取的是可用的服务实例清单,而清单中的服务实例其实就是存储了服务提供方相关元数据的对象,一起来看一下ServiceInstance源码:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1package org.springframework.cloud.client;
2
3import java.net.URI;
4import java.util.Map;
5
6public interface ServiceInstance {
7
8   String getServiceId();
9
10  String getHost();
11
12  int getPort();
13
14  boolean isSecure();
15
16  URI getUri();
17
18  Map<String, String> getMetadata();
19
20  default String getScheme() {
21      return null;
22  }
23}
24
25

它是一个接口,我们可以从实现该接口的实现类对象中获取到服务ID,HOST,PORT,URI,其他元数据以及是否启用HTTPS等基本信息,从这些基本信息中我们完全可以根据服务名来组装真实的调用地址了,特此说明。

通过进一步阅读代码,我们在接口LoadBalancerClient所在的包org.springframework.cloud.client.loadbalancer中发现两个重要的类:LoadBalancerInterceptor和LoadBalancerAutoConfiguration,从名称可知,第一个类是一个负载均衡器拦截器,第二个是负载均衡器的自动化配置类,它们之间的关系可以从下面的类图中得知:

Spring Cloud微服务技术栈(五):客户端负载均衡Spring Cloud Ribbon部分源码分析

我们首先来查看LoadBalancerAutoConfiguration类,看看有没有哪些代码能帮助我们理解负载均衡实现原理。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
1package org.springframework.cloud.client.loadbalancer;
2
3import org.springframework.beans.factory.ObjectProvider;
4import org.springframework.beans.factory.SmartInitializingSingleton;
5import org.springframework.beans.factory.annotation.Autowired;
6import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
7import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
8import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
9import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
10import org.springframework.boot.context.properties.EnableConfigurationProperties;
11import org.springframework.context.annotation.Bean;
12import org.springframework.context.annotation.Configuration;
13import org.springframework.http.client.ClientHttpRequestInterceptor;
14import org.springframework.retry.backoff.BackOffPolicy;
15import org.springframework.retry.support.RetryTemplate;
16import org.springframework.web.client.RestTemplate;
17
18import java.util.ArrayList;
19import java.util.Collections;
20import java.util.List;
21
22/**
23 * Auto configuration for Ribbon (client side load balancing).
24 *
25 * @author Spencer Gibb
26 * @author Dave Syer
27 * @author Will Tran
28 * @author Gang Li
29 */
30@Configuration
31@ConditionalOnClass(RestTemplate.class)
32@ConditionalOnBean(LoadBalancerClient.class)
33@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
34public class LoadBalancerAutoConfiguration {
35
36  @LoadBalanced
37  @Autowired(required = false)
38  private List<RestTemplate> restTemplates = Collections.emptyList();
39
40  @Bean
41  public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
42          final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
43      return () -> restTemplateCustomizers.ifAvailable(customizers -> {
44            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
45                for (RestTemplateCustomizer customizer : customizers) {
46                    customizer.customize(restTemplate);
47                }
48            }
49        });
50  }
51
52  @Autowired(required = false)
53  private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
54
55  @Bean
56  @ConditionalOnMissingBean
57  public LoadBalancerRequestFactory loadBalancerRequestFactory(
58          LoadBalancerClient loadBalancerClient) {
59      return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
60  }
61
62  @Configuration
63  @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
64  static class LoadBalancerInterceptorConfig {
65      @Bean
66      public LoadBalancerInterceptor ribbonInterceptor(
67              LoadBalancerClient loadBalancerClient,
68              LoadBalancerRequestFactory requestFactory) {
69          return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
70      }
71
72      @Bean
73      @ConditionalOnMissingBean
74      public RestTemplateCustomizer restTemplateCustomizer(
75              final LoadBalancerInterceptor loadBalancerInterceptor) {
76          return restTemplate -> {
77                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
78                        restTemplate.getInterceptors());
79                list.add(loadBalancerInterceptor);
80                restTemplate.setInterceptors(list);
81            };
82      }
83  }
84
85  @Configuration
86  @ConditionalOnClass(RetryTemplate.class)
87  public static class RetryAutoConfiguration {
88
89      @Bean
90      @ConditionalOnMissingBean
91      public LoadBalancedRetryFactory loadBalancedRetryFactory() {
92          return new LoadBalancedRetryFactory() {};
93      }
94  }
95
96  @Configuration
97  @ConditionalOnClass(RetryTemplate.class)
98  public static class RetryInterceptorAutoConfiguration {
99      @Bean
100     @ConditionalOnMissingBean
101     public RetryLoadBalancerInterceptor ribbonInterceptor(
102             LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
103             LoadBalancerRequestFactory requestFactory,
104             LoadBalancedRetryFactory loadBalancedRetryFactory) {
105         return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
106                 requestFactory, loadBalancedRetryFactory);
107     }
108
109     @Bean
110     @ConditionalOnMissingBean
111     public RestTemplateCustomizer restTemplateCustomizer(
112             final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
113         return restTemplate -> {
114                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
115                        restTemplate.getInterceptors());
116                list.add(loadBalancerInterceptor);
117                restTemplate.setInterceptors(list);
118            };
119     }
120 }
121}
122
123

给TA打赏
共{{data.count}}人
人已打赏
安全经验

Google Adsense广告设置到位置放置的技巧

2021-10-11 16:36:11

安全经验

安全咨询服务

2022-1-12 14:11:49

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索