Ribbon是什么
Ribbon是一个客户端负载均衡器,它赋予了应用一些支配HTTP与TCP行为的能力,这里的负载均衡是客户端的负载均衡,也有人称为后端负载均衡是进程内负载均衡的一种。Ribbon是SpringCloud生态里的不可缺少的组件,有了它,是个服务的横向扩展更加方便了。此外想Feign和Zuul默认是集成了Ribbon。
Ribbon是Neflix开源的一个组件,目前Ribbon早已进入维护状态,但是就目前的情况来看,Spring Cloud Netflix的一些组件还是可以使用。
Spring Cloud Loadbalancer是Spring Cloud社区开源的组件,目的是替代进入维护状态的Ribbon,但是Loadbalancer还是有很长的一段路要走。
Ribbon入门
由于客户端负载均衡需要从注册中心获取服务列表,所以需要集成注册中心。
创建父级工程cloud-ribbon-practice
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
|
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring.cloud-version>Hoxton.SR3</spring.cloud-version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
|
创建注册中心cloud-eureka-server
1
2
3
4
5
6
|
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
|
配置文件application.xml
1
2
3
4
5
6
7
8
9
10
|
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
|
启动类
1
2
3
4
5
6
7
8
|
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
|
创建源服务工程cloud-ribbon-hello-b1、cloud-ribbon-hello-b2、cloud-ribbon-hello-b3
1
2
3
4
5
6
|
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
|
配置文件application.yml
b1
1
2
3
4
5
6
7
8
9
10
11
|
spring:
application:
name: ribbon-service-b
server:
port: 7777
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
|
b2
1
2
3
4
5
6
7
8
9
10
11
|
spring:
application:
name: ribbon-service-b
server:
port: 7778
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
|
b3
1
2
3
4
5
6
7
8
9
10
11
|
spring:
application:
name: ribbon-service-b
server:
port: 7779
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
|
b1、b2、b3的controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@RestController
public class OrderController {
@Value("${server.port}")
private Integer port;
@Value("${spring.application.name}")
private String name;
@GetMapping("/test")
public String add() {
return "this service name is " + name + " and port is " + port;
}
}
|
b1、b2、b3的启动类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@SpringBootApplication
@EnableDiscoveryClient
public class ServiceB1Application {
public static void main(String[] args) {
SpringApplication.run(ServiceB1Application.class, args);
}
}
@SpringBootApplication
@EnableDiscoveryClient
public class ServiceB2Application {
public static void main(String[] args) {
SpringApplication.run(ServiceB2Application.class, args);
}
}
@SpringBootApplication
@EnableDiscoveryClient
public class ServiceB3Application {
public static void main(String[] args) {
SpringApplication.run(ServiceB3Application.class, args);
}
}
|
创建服务调用者cloud-ribbon-hello-a
spring-cloud-starter-netflix-eureka-client
已经集成了ribbon。不需要额外引入,直接使用即可。
1
2
3
4
5
6
|
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
|
配置文件application.yml
1
2
3
4
5
6
7
8
9
10
11
|
spring:
application:
name: ribbon-hello-a
server:
port: 7776
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
|
启动类,RestTemplate使用了@LoadBalanced
,这样RestTemplate就开启了ribbon的负载均衡了。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@SpringBootApplication
@EnableDiscoveryClient
public class ServiceAApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceAApplication.class, args);
}
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
|
controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@RestController
@RequestMapping("ribbon")
public class TestController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/test")
public String test(){
String body = restTemplate.getForEntity("http://RIBBON-SERVICE-B/test", String.class).getBody();
return body;
}
}
|
测试,使用postman访问几次:http://localhost:7776/ribbon/test。下图可以看出,默认是的负载均衡算法是轮询。
Ribbon实战
从上一节可以看到,开启负载均衡只需通过@LoadBalanced
注解即可。负载均衡中又很多的负载均衡策略,如轮询(Round Robin)、权重(Weight)、ip_hash等。这些丰富的策略让我们在构建应用的时候,有很多选择的余地,可以根据实际的业务场景选择最合适的策略。
在Ribbon中一共提供了7中负载均衡策略:
策略类 |
命名 |
描述 |
RandomRule |
随机策略 |
随机选择Server |
RoundRobinRule |
轮询策略 |
按顺序选择Server |
RetryRule |
重试策略 |
在一个配置时间段内当选择Server不成功,则一直尝试选择一个可用的Server |
BestAvailableRule |
最低并发策略 |
卓哥考察Server,如果Server断路器打开,则忽略,再选择其中并发连接最低的Server |
AvailabilityFilteringRule |
可用过滤策略 |
过滤一直连接失败并标记为circuit tripped的Server,过滤掉那些高并发连接的Server(active connections超过配置的阈值) |
ResponseTimeWeightedRule |
响应时间加权策略 |
已经被弃用,作用同WeightedResponseTimeRule |
WeightedResponseTimeRule |
响应时间加权策略 |
根据Server的响应时间分配权重,响应时间越长,权重越低,被选中的概率就越低。响应时间越短,权重越高,被选择到的概率越高 |
ZoneAvoidanceRule |
区域权衡策略 |
综合判断Server所在区域的性能和Server的可用性轮询选择Server,并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有Server |
在上面的入门案例中Ribbon的默认负载均衡策略是轮询策略,
Ribbon自定义配置负载均衡策略
全局配置
使用Ribbon时配置全局的负载均衡策略,需要加一个配置类。改配置类需要被@ComponentScan
扫描到才能全局生效。
1
2
3
4
5
6
7
|
@Configuration
public class GlobalRuleConfig {
@Bean
public IRule ribbonRule() {
return new RandomRule();
}
}
|
上面配置了随机的策略,多次访问http://localhost:7776/ribbon/test。就可看到Server的选择就变成了随机了。同样了可以选择其他的Ribbon已经实现的策略,也可以自定义负载均衡策略。
基于@RibbonClient
或@RibbonClients
注解的配置
配置类,注意:编写自定义配置类,需要特别注意的是官方文档明确给出了警告:这个自定义配置类不能放在@ComponentScan所扫描的包以及其子包下(即不能放在主启动类所在的包及其子包下,因此我们需要新建一个包来放该配置类),否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,也就达不到特殊化定制的目的了
1
2
3
4
5
6
7
|
@Configuration
public class AnnoRuleConfig {
@Bean
public IRule ribbonRule() {
return new RandomRule();
}
}
|
启动类的配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient(name = "ribbon-service-b", configuration = AnnoRuleConfig.class)
public class ServiceAApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceAApplication.class, args);
}
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
|
通过@RibbonClient
指定某个服务的负载均衡策略,其他没有被指定的,就是用默认的负载均衡策略。该注解可以把其他的配置类作为另外一个IOC容器导入到应用中,相当于加载了两个完全不相干的Spring的beans配置文件,此时应用中会有两个IOC容器。
1
|
@RibbonClient(name = "RIBBON-SERVICE-B", configuration = AnnoRuleConfig.class)
|
也可以使用一下的方式,指定多个服务的负载均衡策略
1
2
3
4
|
@RibbonClients(value = {
@RibbonClient(name = "RIBBON-SERVICE-B", configuration = AnnoRuleConfig.class),
@RibbonClient(name = "RIBBON-SERVICE-C", configuration = AnnoRuleConfig.class)
})
|
基于配置文件
下面对服务ribbon-service-b
的负载均衡策略使用
1
2
3
|
RIBBON-SERVICE-B:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
|
Ribbon超时与重试
使用HTTP发起请求难免会发生问题,在F版开始Ribbon的重试机制默认是开启的,需要添加对超时时间与重试策略的配置。列入下面ribbon-service-b
服务的配置
1
2
3
4
5
6
7
8
|
RIBBON-SERVICE-B:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
ConnectTimeout: 3000
ReadTimeout: 60000
MaxAutoRetries: 3 #对第一次请求的服务的重试次数
MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量(不包括第一个服务)
OkToRetryOnAllOperations: true
|
也可以全局配置
1
2
3
4
5
6
|
ribbon:
ConnectTimeout: 3000
ReadTimeout: 60000
MaxAutoRetries: 3 #对第一次请求的服务的重试次数
MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量(不包括第一个服务)
OkToRetryOnAllOperations: true
|
一般Ribbon都是搭配OpenFeign这类Http客户端或者其他RPC使用。因为这样去调用远程服务会更加优雅和方便。而OpenFeign默认是继承了Ribbon,对于Ribbon的超时时间配置也是很简单。
对于网络抖动这些可以使用spring-retry
,spring-retry是spring提供的一个基于spring的重试框架,非常好用。
Ribbon饥饿加载
Ribbon在进行客户端负载均衡的时候,并不是启动时就加载上下文,而是在实际请求的时候采取创建。因为要加载上下文的原因,在第一次调用时可能会很慢,甚至导致超时。所以我们可以指定Ribbon客户端开启立即加载(饥饿加载),在应用启动的时候就立即加载所有配置项的应用程序上下文。
1
2
3
4
|
ribbon:
eager-load:
clients: ribbon-service-b, ribbon-service-order
enabled: true
|
自定义Ribbon客户端
在Ribbon的1.2.0版本之后,就可以使用配置文件来定制Ribbon客户端,其实实质就是使用配置文件来指定一些默认加载类,从而更改Ribbon客户端的行为,并且使用这种方式优先级最高,优先级高于使用注解@RibbonClient
指定的配置和源码中加载的相关的Bean。看下表:
配置项 |
说明 |
.ribbon.NFLoadBalancerClassName |
指定ILoadBalancer的实现类 |
.ribbon.NFLoadBalancerRuleClassName |
指定IRule的实现类 |
.ribbon.NFLoadBalancerPingClassName |
指定IPing的实现类 |
.ribbon.NiWSServerListClassName |
指定ServerList的实现类 |
.ribbon.NIWSServerListFilterClassName |
指定ServerListFilter的实现类 |
例如:这里使用的实现的RIbbon提供的实现
1
2
3
4
|
RIBBON-SERVICE-B:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
NiWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
|
Ribbon脱离Eureka使用
在默认的情况下,Ribbon客户端需要从Eureka注册中心读取服务注册信息列表,来达到一种动态负载均衡的功能。当使用的注册中心是公共的注册中心,例如:社区公益Eureka(http://eureka.springcloud.cn),所以就不要从Eureka中读取服务列表了。而是从Ribbon客户端自行指定原服务地址,让Rabbon脱离使用。配置如下:
1
2
3
4
5
6
7
|
ribbon:
eureka:
enabled: false
#RIBBON-SERVICE-B服务的获取地址
RIBBON-SERVICE-B:
ribbon:
listOfServers: http://localhost:8088/
|
Ribbon进阶
核心工作原理
Ribbon的核心接口:
接口 |
描述 |
默认实现 |
IClientConfig |
定义Ribbon中管理配置的接口 |
DefaultClientConfigImpl |
IRule |
定义Ribbon中负载均衡策略的接口 |
ZoneAdvoidanceRule |
IPing |
定义定期Ping服务检查可用性的接口 |
DummyPing |
ServerList<Server> |
定义获取服务列表方法的接口 |
ConfigurationBasedServerList |
ServerListFilter<Server> |
定义特定期望获取服务列表方法的接口 |
ZonePreferenceServerListFilter |
ILoadBalancer |
定义负载均衡选择服务的核心方法的接口 |
ZoneAwareLoadBalancer |
ServerListUpdater |
为DynamicServerListLoadBalancer定义动态更新服务列表的接口 |
PollingServerListUpdater |
Ribbon完全是基于这些接口上建立起来的,是Ribbon的核心。了解这些核心的类的功能对于理解Ribbon的原理和扩展很有利。
在之前的例子中,使用Ribbon负载均衡都是通过在RestTemplate的Bean上添加注解@LoadBalanced
,使得RestTemplate拥有了负载均衡的能力。
LoadBalanced源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/**
* Annotation to mark a RestTemplate or WebClient bean to be configured to use a
* LoadBalancerClient.
* @author Spencer Gibb
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
|
在注释中可以看到:该注解标记在RestTemplate或者其他的WebClient的Bean上,来使用LoadBalancerClient。
**LoadBalancerClient:**该接口扩展自ServiceInstanceChooser
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
|
public interface LoadBalancerClient extends ServiceInstanceChooser {
/**
* Executes request using a ServiceInstance from the LoadBalancer for the specified
* service.
* @param serviceId The service ID to look up the LoadBalancer.
* @param request Allows implementations to execute pre and post actions, such as
* incrementing metrics.
* @param <T> type of the response
* @throws IOException in case of IO issues.
* @return The result of the LoadBalancerRequest callback on the selected
* ServiceInstance.
*/
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
/**
* Executes request using a ServiceInstance from the LoadBalancer for the specified
* service.
* @param serviceId The service ID to look up the LoadBalancer.
* @param serviceInstance The service to execute the request to.
* @param request Allows implementations to execute pre and post actions, such as
* incrementing metrics.
* @param <T> type of the response
* @throws IOException in case of IO issues.
* @return The result of the LoadBalancerRequest callback on the selected
* ServiceInstance.
*/
<T> T execute(String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest<T> request) throws IOException;
/**
* Creates a proper URI with a real host and port for systems to utilize. Some systems
* use a URI with the logical service name as the host, such as
* http://myservice/path/to/service. This will replace the service name with the
* host:port from the ServiceInstance.
* @param instance service instance to reconstruct the URI
* @param original A URI with the host as a logical service name.
* @return A reconstructed URI.
*/
URI reconstructURI(ServiceInstance instance, URI original);
}
|
ServiceInstanceChooser:
1
2
3
4
5
6
7
8
9
|
public interface ServiceInstanceChooser {
/**
* Chooses a ServiceInstance from the LoadBalancer for the specified service.
* @param serviceId The service ID to look up the LoadBalancer.
* @return A ServiceInstance that matches the serviceId.
*/
ServiceInstance choose(String serviceId);
}
|
- ServiceInstance choose(String serviceId):根据ServiceId,结合负载均衡器选择一个服务实例
- T execute(String serviceId, LoadBalancerRequest request):使用来自LoadBalancer的ServiceInstance为指定的服务执行请求
- T execute(String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest request):使用来自LoadBalancer的ServiceInstance为指定的服务执行请求,是上一个方法的重载,在实现类中可以看到它们的关系,就是前一个方法的细节实现、
- URI reconstructURI(ServiceInstance instance, URI original):使用注解ip和port构建特定的URL以供Ribbon内部使用。Ribbon使用具有逻辑服务名称的URL作为host,例如:http://service-b/order/add。
从这些方法的功能可以知道这两个接口的重要性了。这两个接口的同一包下有一个类LoadBalancerAutoConfiguration
。LoadBalancerAutoConfiguration
在org.springframework.cloud.client.loadbalancer
包下,在spring-cloud-commons
里面。该自动配置类正式Ribbon的核心配置类。
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
|
@Configuration(proxyBeanMethods = false)
@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();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class 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);
};
}
}
/**
* Auto configuration for retry mechanism.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RetryTemplate.class)
public static class RetryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryFactory() {
return new LoadBalancedRetryFactory() {
};
}
}
/**
* Auto configuration for retry intercepting mechanism.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RetryTemplate.class)
public static class RetryInterceptorAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRetryProperties properties,
LoadBalancerRequestFactory requestFactory,
LoadBalancedRetryFactory loadBalancedRetryFactory) {
return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
requestFactory, loadBalancedRetryFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
}
|
这就清晰起来了,他的配置加载时机是当前工程环境必须有RestTemplate的实例和必须初始化了LoadBalancerClient的实现类。
1
2
|
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
|
LoadBalancerRequestFactory:用于创建LoadBalancerRequest给LoadBalancerInterceptor使用。
LoadBalancerInterceptorConfig:维护了LoadBalancerInterceptor与RestTemplateCustomizer的实例。
- LoadBalancerInterceptor:拦截每一次的HTTP请求,将请求绑定金Ribbon的负载均衡的生命周期。
- RestTemplateCustomizer:为每一个Restemplate绑定LoadBalancerInterceptor拦截器。
LoadBalancerInterceptor的作用已经和贴近答案了。
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
|
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) {
// for backwards compatibility
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
}
|
在源码可以看到它是通过ClientHttpRequestInterceptor实现每次对HTTP请求的拦截,ClientHttpRequestInterceptor类是Spring中维护的请求拦截器,实现它的intercept方法就可以使得请求进入方法内,从而Ribbon就可以做一些自己的处理了。
在使用RestTemplate请求服务时使用的URI:http://serviceName/path/to/service,通过getHost拿到服务名称serviceName。LoadBalancer有两个:RibbonLoadBalancerClient和BlockingLoadBalancerClient。这里只说RibbonLoadBalancerClient。
LoadBalancerInterceptor中的intercept方法,最终调用的是RibbonLoadBalancerClient的execute方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
// 拿到负载均衡器的实现
ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
// 拿到具体的Server
Server server = this.getServer(loadBalancer, hint);
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, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request);
}
}
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default");
}
|
对于chooseServer是接口ILoadBalancer的方法,这里就先看一下其中的一个实现BaseLoadBalancer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public Server chooseServer(Object key) {
if (this.counter == null) {
this.counter = this.createCounter();
}
this.counter.increment();
if (this.rule == null) {
return null;
} else {
try {
return this.rule.choose(key);
} catch (Exception var3) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", new Object[]{this.name, key, var3});
return null;
}
}
}
|
最后是通过:rule.choose(key)
拿到Server,而rule就是IRule。
在RibbonClientConfiguration
中初始化了上面表格提到几个核心类
- 初始化ribbonRule: ZoneAvoidanceRule
- 初始化ribbonPing:DummyPing
- 初始化ribbonServerList:ConfigurationBasedServerList
- 初始化ServerListUpdater:new PollingServerListUpdater(config)
- 初始化ILoadBalancer:ZoneAwareLoadBalancer
- 初始化ribbonServerListFilter:ZonePreferenceServerListFilter
- 初始化ribbonLoadBalancerContext:RibbonLoadBalancerContext
- 初始化serverIntrospector:DefaultServerIntrospector
关于BlockingLoadBalancerClient:
Spring Cloud Hoxton.RELEASE 版本发布之后,新增了一个新的负载均衡器实现BlockingLoadBalancerClient
。它是第一个包含阻塞式
和非阻塞式
负载均衡器客户端实现的版本,作为已进入维护模式的Netflix Ribbon的替代方案。
如果想在 RestTemplate
使用新的 BlockingLoadBalancerClient
, 需要增加 spring-cloud-loadbalancer
的依赖,否则默认使用RibbonLoadBalancerClient
。