SpringCloud之Ribbon
1、简介
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的。
2、客户端负载均衡
客户端负载均衡和服务端负载均衡最大的不同点在于上面所提到服务清单所存储的位置。在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清单,而这些服务端端清单来自于服务注册中心,比如Eureka。
同服务端负载均衡的架构类似,在客户端负载均衡中也需要心跳去维护服务端清单的健康性,默认会创建针对各个服务治理框架的Ribbon自动化整合配置,比如Eureka中的org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration。
通过Spring Cloud Ribbon的封装,我们在微服务架构中使用客户端负载均衡调用非常简单,只需要如下两步:
▪️服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心。
▪️服务消费者直接通过调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用。
3、简单示例入门
1、搭建好SpringCloud基本框架,启动Eureka。
2、创建两个服务提供者。
3、创建服务消费者,配置RestTemplate,使用@LoadBalance实现负载均衡
4、RestTemplate基础用法
在SpringCloud中,使用RestTemplate需先将RestTemplate注入Spring容器中。
编写RestTemplate配置类
@Configuration
public class RestTemplateConfiguration {
@Bean
// 开启负载均衡功能
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
在Service层中,使用getForObject、getForEntity和postForEntity对服务提供者进行调用。
@Autowired
private RestTemplate restTemplate;
@Override
public ResultJson login(User user) {
ResponseEntity<ResultJson> resultJsonResponseEntity = restTemplate.postForEntity("http://spring-cloud-service-user/user/login", user, ResultJson.class, user);
return resultJsonResponseEntity.getBody();
}
@Override
public ResultJson getInfo(Integer id) {
ResponseEntity<ResultJson> resultJsonResponseEntity = restTemplate.getForEntity("http://spring-cloud-service-user/user/info?id={1}", ResultJson.class, id);
return resultJsonResponseEntity.getBody();
}
@Override
public String helloWorld(String message) {
return restTemplate.getForObject("http://spring-cloud-service-admin/helloWorld?message=" + message, String.class);
}
5、RestTemplate中Get和Post API分析
在RestTemplate中,对GET请求可以通过如下两个方法进行调用实现。
第一种:getForEntity函数。该方法返回的是ResponseEntity,该对象是Spring对HTTP请求响应的封装,其中主要存储了HTTP的几个重要元素,比如HTTP请求状态码的枚举对象HttpStatus(也就是我们常说的404、500这些错误码)、在它的父类HttpEntity中还存储着HTTP请求的头信息对象HttpHeaders以及泛型类型的请求体对象。
第二种:getForObject函数。该方法可以理解为对getForEntity的进一步封装,它通过HttpMessageConverterExtractor对HTTP的请求响应体body内容进行对象转换,实现请求直接返回包装好的对象内容。
@Nullable
<T> T getForObject(String var1, Class<T> var2, Object... var3) throws RestClientException;
@Nullable
<T> T getForObject(String var1, Class<T> var2, Map<String, ?> var3) throws RestClientException;
@Nullable
<T> T getForObject(URI var1, Class<T> var2) throws RestClientException;
<T> ResponseEntity<T> getForEntity(String var1, Class<T> var2, Object... var3) throws RestClientException;
<T> ResponseEntity<T> getForEntity(String var1, Class<T> var2, Map<String, ?> var3) throws RestClientException;
<T> ResponseEntity<T> getForEntity(URI var1, Class<T> var2) throws RestClientException;
在上面的源代码中,可以看到getForEntity函数返回的对象为ResponseEntity对象,该对象中包含status、header和body信息。status为通信状态字,例如200。body则为请求时传入的class类型。
Post请求Object和Entity方法接口源码。
@Nullable
<T> T postForObject(String var1, @Nullable Object var2, Class<T> var3, Object... var4) throws RestClientException;
@Nullable
<T> T postForObject(String var1, @Nullable Object var2, Class<T> var3, Map<String, ?> var4) throws RestClientException;
@Nullable
<T> T postForObject(URI var1, @Nullable Object var2, Class<T> var3) throws RestClientException;
<T> ResponseEntity<T> postForEntity(String var1, @Nullable Object var2, Class<T> var3, Object... var4) throws RestClientException;
<T> ResponseEntity<T> postForEntity(String var1, @Nullable Object var2, Class<T> var3, Map<String, ?> var4) throws RestClientException;
<T> ResponseEntity<T> postForEntity(URI var1, @Nullable Object var2, Class<T> var3) throws RestClientException;
在post请求中,比get请求多了个Object参数,该参数可以是一个普通对象,也可以是HttpEntity对象。如果是普通对象,RestTemplate会将请求对象转换为一个HttpEntity对象来处理,且该对象会被视为完整的body。
public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
RequestCallback requestCallback = this.httpEntityCallback(request, responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = this.responseEntityExtractor(responseType);
return (ResponseEntity)nonNull(this.execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables));
}
public <T> RequestCallback httpEntityCallback(@Nullable Object requestBody, Type responseType) {
return new RestTemplate.HttpEntityRequestCallback(requestBody, responseType);
}
----------------------------------------------------------------------------------------------
public HttpEntityRequestCallback(@Nullable Object requestBody, @Nullable Type responseType) {
super(responseType);
if (requestBody instanceof HttpEntity) {
this.requestEntity = (HttpEntity)requestBody;
} else if (requestBody != null) {
this.requestEntity = new HttpEntity(requestBody);
} else {
this.requestEntity = HttpEntity.EMPTY;
}
}
在RestTemplate中,还提供了了put,delete,options,patch、exchange、execute相关的方法。
6、负载均衡策略
1、AbstractLoadBalancerRule
负载均衡策略的抽象类,在该抽象类中定义了负载均衡器ILoadBalancer对象,该对象能够在具体实现选择服务策略时,获取到一些负载均衡器中维护的信息来作为分配依据,并以此设计一些算法来实现针对特定场景的高效策略。
2、ClientConfigEnabledRoundRobinRule
该策略较为特殊,我们一般不直接使用它,因为它本身并没有实现什么特殊的处理逻辑,正如下面的源码所示,在它的内部定义了一个RoundRobinRule策略,而choose函数的实现也正是使用了RoundRobinRule的线性轮询机制,所以它实现的功能实际上与RoundRobinRule相同。
1、BestAvailableRule
该策略继承自ClientConfigEnabledRoundRobinRule,在实现中它注入了负载均衡器的统计对象LoadBalancerStats,同时在具体的choose算法中利用LoadBalancerStats保存的实例统计信息来选择满足要求的实例。从如下源码中我们可以看到,它通过遍历负载均衡器中维护的所有服务实例,会过滤掉故障的实例,并找出并发请求数最小的一个,所以该策略的特性是可选出最空闲的实例。
同时,由于该算法的核心依据是统计对象LoadBalancerStats,当其为空的时候,该策略是无法执行的。所以从源码中我们可以看到,当loadBalancerStats为空的时候,它会采用父类的线性轮询策略,正如我们在介绍ClientConfigEnabledRoundRobinRule时那样,它的子类在无法满足实现高级策略的时候,可以使用线性轮询策略的特性。后面将要介绍的策略因为也都继承自ClientConfigEnabledRoundRobinRule,所以它们都会具有这样的特性。
2、PredicateBasedRule
这是一个抽象策略,它也继承了ClientConfigEnabledRoundRobinRule,从其命名中可以猜出这是一个基于Predicate实现的策略,Predicate是Google Guava Collections工具对集合进行过滤掉条件接口。
-
ZoneAvoidanceRule
ZoneAvoidanceRule是PredicateBasedRule的具体实现类。它使用了CompositePredicate来进行服务实例清单的过滤。这是一个组合过来条件,在其构造函数中,它以ZoneAvoidanceRule为主过滤条件,AvailabilityPredicate为次过滤条件初始化了组合过滤条件的实例。
-
AvailabilityFilteringRule
该策略继承自上面介绍的抽象策略PredicateBasedRule,所以它也继承了“先过滤清单,再轮询选择”的的基本处理逻辑,其中过滤条件使用了AvailabilityPredicate。简单地说,该策略通过线性抽样的方式直接尝试寻找可用且较空闲的实例来使用,优化了父类每次都要遍历所有实例的开销。
3、RoundRobinRule
该策略实现了按照线性轮询的方式的方式一次选择每个服务实例的功能。它的具体实现如下,其详细结构与RandomRule非常类似。除了循环条件不同外,就是从可用列表中获取所谓的逻辑不通。从循环条件中,我们可以看到增加了一个count计数变量,该变量会在每次循环之后累加,也就是说,如果一直选择不到server超过10次,那么就会结束尝试
-
WeightedResponseTimeRule
该策略是对RoundRobinRule的扩展,增加了根据实例等运行情况来计算权重,并根据权重来挑选实例,以达到更优的分配效果,它的实现主要有三个核心内容。定时任务,权重计算,和实例选择。
-
ResponseTimeWeightedRule
该策略也是对RoundRobinRule的扩展,根据 响应时间的情况来设置权重,并根据权重来挑选实例。
4、RandomRule
该策略实现了从服务实例清单中随机选择一个服务实例的功能。
5、RetryRule
该策略实现了一个具备重试机制的实例选择功能。默认使用了RoudRobinRule实例。而在choose方法中则实现了对内部定义的策略进行反复尝试的策略,若期间能够选择到具体的服务实例就反悔,若选择不到就根据设置结束时间为阀值(maxRetryMillis参数定义的值+choose方法开始执行的时间戳),当超过该阀值就返回null。