【Spring Cloud H 版教程】第五篇:负载均衡组件Ribbon

1.概述

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 和 服务、注册中心之间的关系图:

【Spring Cloud H 版教程】第五篇:负载均衡组件Ribbon_第1张图片

2.快速入门

2.1准备工作

为了演示 Ribbon 的负载均衡功能,我启动两个 cloud-eureka-client 服务实例作为提供者服务,端口分别为 8088 和 8089 。每个服务都包含一个 sayHello 接口方法:
【Spring Cloud H 版教程】第五篇:负载均衡组件Ribbon_第2张图片

@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;
    }
}

2.2创建一个消费者服务

第一步: 创建一个消费者服务 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 。
【Spring Cloud H 版教程】第五篇:负载均衡组件Ribbon_第3张图片
第二步:在 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);
    }

}

2.3测试负载均衡效果

在浏览器上一直访问地址 http://localhost:8089/sayHello/luke ,可以看到请求的响应值依次交替来自 8088 和 8089 。Ribbon 默认使用了轮询负载均衡策略:
【Spring Cloud H 版教程】第五篇:负载均衡组件Ribbon_第4张图片
Ribbon 获取了注册中心上所有实例的地址信息,消费者服务 cloud-ribbon-rest-service 借助于 Ribbon 从服务 cloud-eureka-client 两个实例中依次轮询选出一个目标服务,然后再交给 RestTemplate 执行远程调用。
【Spring Cloud H 版教程】第五篇:负载均衡组件Ribbon_第5张图片
我们知道 RestTemplate 整合 Ribbon 是通过添加注解 @LoadBalanced 实现的,那么如果去掉了该注解,也就是不使用 Ribbon ,那么 RestTemplate 就必须使用具体的IP地址和端口才能访问:
【Spring Cloud H 版教程】第五篇:负载均衡组件Ribbon_第6张图片

3.超时机制

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 超时异常提示信息:
【Spring Cloud H 版教程】第五篇:负载均衡组件Ribbon_第7张图片

4.负载均衡策略

4.1策略种类

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

4.2修改默认策略

我们可以根据需求调整 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 。

你可能感兴趣的:(Spring,Cloud,Alibaba,Ribbon,SpringCloud,负载均衡,Java,微服务)