Ribbon is a client-side load balancer that gives you a lot of control over the behavior of HTTP and TCP clients. Feign already uses Ribbon, so, if you use @FeignClient, this section also applies.
Ribbon 是一个客户端负载均衡器,可以让你对 HTTP 和 TCP 客户端的行为进行大量控制。Feign 内部也使用了 Ribbon 。
Ribbon 作为客户端负载均衡器,维护着所有节点的地址清单,这些地址清单通常来自于注册中心,当集成了 Ribbon 的客户端服务对目标服务(部署多个实例)发起远程调用的时候,Ribbon 会通过负载均衡算法选出一个目标服务实例,接着将实例的地址信息交给客户端服务的 HTTP/TCP 客户端执行调用,在调用过程中 Ribbon 还提供了一系列完善的机制,例如超时、重试等。在 Spring Cloud 微服务架构中,一个完整的远程调用通常需要一个调用客户端和一个负载均衡器,例如 restTemplate + Ribbon 或者 OpenFeign + Ribbon 。下图是 Ribbon 和 服务、注册中心之间的关系图:
为了演示 Ribbon 的负载均衡功能,我启动两个 cloud-eureka-client 服务实例作为提供者服务,端口分别为 8088 和 8089 。每个服务都包含一个 sayHello 接口方法:
@Slf4j
@RestController
public class EurekaClientController {
@Value("${server.port}")
private Integer port;
@GetMapping("/sayHello/{name}")
public String sayHello(@PathVariable("name") String name){
return "hello," + name+",from "+port;
}
}
第一步: 创建一个消费者服务 cloud-ribbon-rest-service ,加入 Ribbon 相关依赖 spring-cloud-starter-netflix-ribbon:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-ribbonartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
dependencies>
由于 Eureka 默认包含了 Ribbon ,所以 POM 也可以不需要引入 spring-cloud-starter-netflix-ribbon 。
第二步:在 application.yml 文件配置连接注册中心 Eureka,注册中心地址为 http://localhost:7001
。
server:
port: 8090
spring:
application:
name: cloud-ribbon-rest-service
#连接Eureka注册中心
eureka:
instance:
prefer-ip-address: true #使用ip地址注册
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://admin:123456@localhost:7001/eureka
第三步:在工程主启动类中启用 RestTemplate 。我们将使用 RestTemplate + Ribbon 的方式演示 Ribbon 相关功能,关于 OpenFeign 将在后面的课程中单独进行详细介绍。
@SpringBootApplication
@EnableDiscoveryClient
public class RibbonRestServiceMain8090 {
public static void main(String[] args) {
SpringApplication.run(RibbonRestServiceMain8090.class,args);
}
@Bean
@LoadBalanced //RestTemplate整合负载均衡器Ribbon
RestTemplate restTemplate(){
return new RestTemplate();
}
}
第四步: 编写一个 Controller 类,通过 RestTemplate API 请求提供者服务 cloud-ribbon-rest-service:
@Slf4j
@RestController
public class RestTemplateController {
/**提供者服务cloud-eureka-client地址*/
public static final String EUREKA_SERVICE_URL = "http://cloud-eureka-client";
@Resource
private RestTemplate restTemplate;
/**
* 使用RestTemplate+Ribbon 远程调用 cloud-eureka-client 服务
* @param name
* @return
*/
@GetMapping("/consumer/sayHello/{name}")
public String consumerSayHello(@PathVariable("name") String name) {
return restTemplate.getForObject(EUREKA_SERVICE_URL+"/sayHello/"+name,String.class);
}
}
在浏览器上一直访问地址 http://localhost:8089/sayHello/luke ,可以看到请求的响应值依次交替来自 8088 和 8089 。Ribbon 默认使用了轮询负载均衡策略:
Ribbon 获取了注册中心上所有实例的地址信息,消费者服务 cloud-ribbon-rest-service 借助于 Ribbon 从服务 cloud-eureka-client 两个实例中依次轮询选出一个目标服务,然后再交给 RestTemplate 执行远程调用。
我们知道 RestTemplate 整合 Ribbon 是通过添加注解 @LoadBalanced 实现的,那么如果去掉了该注解,也就是不使用 Ribbon ,那么 RestTemplate 就必须使用具体的IP地址和端口才能访问:
Ribbon 还提供了超时配置,用于控制服务远程调用的超时时间:
ribbon:
ReadTimeout: 5000 #读取返回数据超时时间
ConnectTimeout: 5000 #连接超时时间
为了测试超时是否起作用,我修改了 cloud-eureka-client 中 EurekaClientController 的代码,使得业务处理时间超过 10 秒:
@GetMapping("/sayHello/{name}")
public String sayHello(@PathVariable("name") String name){
log.info("name:{}",name);
try {
TimeUnit.SECONDS.sleep(10); //睡眠十秒
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello," + name+",from "+port;
}
然而测试发现,Ribbon 的超时配置对 RestTemplate 并不起作用,客户端服务 cloud-ribbon-rest-service 仍然会等待结果的返回,并不会抛出 TimeOut 等异常信息,这是为什么呢?
当使用 RestTemplate 远程调用服务时,内部会通过 SimpleClientHttpRequestFactory 来创建请求对象 ClientHttpRequest,进入 SimpleClientHttpRequestFactory 的 createRequest() 方法:
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
//打开Http连接
HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
//设置HTTP连接对象参数
prepareConnection(connection, httpMethod.name());
if (this.bufferRequestBody) {
return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
}
else {
return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
}
}
进入到 prepareConnection() 方法,你可以看到连接超时时间和读取超时时间并不来自于 Ribbon 的配置,而是来自 SimpleClientHttpRequestFactory 自身的属性值:
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
if (this.connectTimeout >= 0) {
connection.setConnectTimeout(this.connectTimeout);//设置连接超时时间
}
if (this.readTimeout >= 0) {
connection.setReadTimeout(this.readTimeout);//设置读取超时时间
}
//......省略部分代码
connection.setRequestMethod(httpMethod);
}
所以我们要想设置 RestTemplate 的超时时间,只能通过修改 SimpleClientHttpRequestFactory 的 connectTimeout 和 readTimeout 值,RestTemplate 允许在构造函数中传入 SimpleClientHttpRequestFactory 实例对象,所以我们可以这样修改:
@Bean
@LoadBalanced //RestTemplate整合负载均衡器Ribbon
RestTemplate restTemplate(){
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(5000);//设置连接超时时间
requestFactory.setReadTimeout(5000);//设置读取超时时间
return new RestTemplate(requestFactory);
}
设置之后重启 cloud-ribbon-rest-service 服务,再次请求 http://localhost:8090/consumer/sayHello/luke 即可看到 Read Time out 超时异常提示信息:
Ribbon 提供了多种负载均衡策略,默认使用了轮询策略,即 RoundRobinRule :
负载均衡策略 | 策略中文名称 | 具体含义 |
---|---|---|
RandomRule | 随机策略 | 随机选择server |
RoundRobinRule | 轮询策略 | 按照顺序选择server(ribbon默认策略) |
RetryRule | 重试策略 | 在一个配置时间段内,当选择server不成功,则一直尝试选择一个可用的server |
BestAvailableRule | 最低并发策略 | 逐个考察server,如果server断路器打开,则忽略,再选择其中并发链接最低的server |
AvailabilityFilteringRule | 可用过滤策略 | 过滤掉一直失败并被标记为circuit tripped的server,过滤掉那些高并发链接的server(active connections超过配置的阈值) |
ResponseTimeWeightedRule | 响应时间加权重策略 | 根据server的响应时间分配权重,响应时间越长,权重越低,被选择到的概率也就越低。响应时间越短,权重越高,被选中的概率越高,这个策略很贴切,综合了各种因素,比如:网络,磁盘,io等,都直接影响响应时间 |
ZoneAvoidanceRule | 区域权重策略 | 综合判断server所在区域的性能,和server的可用性,轮询选择server并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有server |
我们可以根据需求调整 Ribbon 的负载均衡策略,首先定义一个配置类,返回 IRule 实例:
@Configuration
public class MySelfRule {
@Bean
public IRule myRule(){
/**随机负载均衡策略*/
return new RandomRule();
//return new RoundRobinRule();
}
}
接下来在主启动类使用 @RibbonClient 注解指定策略配置类即可:
@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient(name = "*",configuration = MySelfRule.class) //修改Ribbo负载均衡策略
public class RibbonRestServiceMain8090 {
public static void main(String[] args) {
SpringApplication.run(RibbonRestServiceMain8090.class,args);
}
//.....省略部分代码
}
最后在浏览器不断访问地址 http://localhost:8090/consumer/sayHello/luke ,可以看到响应结果随机来自 8088 和 8089 。