SpringCloud Ribbon 负载均衡

 

Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具。可以将面向服务的 REST 模板请求自动转化成客户端负载均衡的服务调用。Spring Cloud Ribbon 虽然是一个工具类框架,但它不像服务注册中心、配置中心、API网关那样需要独立部署。但是它几乎存在于每一个 Spring Cloud 构建的微服务和基础设置中。因为微服务间的调用,API 网关的请求转发等内容,实际上都是通过 Ribbon 来实现的,包括 Feign 它也是基于 Ribbon 实现的工具。所以 Spring Cloud Ribbon 的理解和使用,对于我们使用 Spring Cloud 来构建微服务非常重要。

一、概述


【1】是什么:Spring Cloud Ribbon 是基于 Netflix Ribbon实现的一套客户端的负载均衡的工具。简单的说,Ribbon是 Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将 Netflix 的中间层服务连接在一起。Ribbon 客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说就是在配置文件中列出 Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮你基于某种规则(如单轮询,随机连接等)去连接这些机器。我们也很容易使用 Ribbon实现自定义的负载均衡算法。
【2】作用:LB,即负载平衡(Load Balance)在微服务或分布式集群中经常用的一种应用。负载平衡简单的说,就是将用户的请求平摊的分配到多个服务,而达到服务的HA(高可用)。常见的负载均衡软件Nginx,LVS,硬件F5等。相应的中间件,例如:Dubbo 和 SpringCloud 中均给我们提供了负载均衡,SpinrgCloud 的负载均衡算法可以自定义。
【3】主要分为两种:1)、集中式LB:偏硬件,即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件F5,也可以是软件Nginx),由该设置负责把访问请求通过某种策略转发至服务的提供方; 2)、进程内LB:将 LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。

Ribbon 就属于进程内 LB,它只是一个库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。 

二、Ribbon 初步配置


【1】修改客户端,例如:microservicecloud-consumer-dept-80 工程的 pom.xml 文件;

 1 <!-- Ribbon相关 -->
 2 <dependency>
 3     <groupId>org.springframework.cloud</groupId>
 4     <artifactId>spring-cloud-starter-eureka</artifactId>
 5 </dependency>
 6 <dependency>
 7     <groupId>org.springframework.cloud</groupId>
 8     <artifactId>spring-cloud-starter-ribbon</artifactId>
 9 </dependency>
10 <dependency>
11     <groupId>org.springframework.cloud</groupId>
12     <artifactId>spring-cloud-starter-config</artifactId>
13 </dependency>
14 <dependency>
15     <groupId>org.springframework.boot</groupId>
16     <artifactId>spring-boot-starter-web</artifactId>
17 </dependency>

【2】修改 application.yml,追加 eureka 的服务注册地址;

1 eureka:
2   client:
3     register-with-eureka: false
4     service-url: 
5       defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/  

【3】对 ConfigBean 配置类,添加新注解 @LoadBalance 获得 RestTemplate 时,加入 Ribbon 的配置;

1 @LoadBalanced//Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端       负载均衡的工具。
2 public RestTemplate getRestTemplate()
3 {
4     return new RestTemplate();
5 }

【4】主启动类 DeptConsumer80_App 添加 @EnableEurekaClient

1 @EnableEurekaClient
2 public class DeptConsumer80_App
3 {

【5】修改 deptController_Consumer 客户端访问类;

1 //微服务的名称MICROSERVICECLOUD-DEPT
2 private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-DEPT";
3 
4 @RequestMapping(value="/comsumer/dept/add")
5 public boolean add(Consumer comsumer){
6     return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",comsumer,Boolean.class);
7 }

【6】先启动3个 eureka 集群后,在启动 microservicecloud-provider-dept-8001 并注册进 eureka;
【7】启动 microservicecloud-consumer-dept-80;
【8】小结:Ribbon 和 Eureka 整合后 Consumer 可以直接调用服务而不用关心服务和端口号。

三、Ribbon 负载均衡


【1】Ribbon 在工作时分为两步:
   ①、先选择EurekaServer,它优先选择在同一个区域内负载较少的server
   ②、根据用户指定的策略,再从 server取到的服务注册列表中选择一个地址;
【2】实操:①、参考 microservicecloud-provide-dept-8001,创建 microservicecloud-provide-dept-8002 和 8003;
   ②、新建 8002/8003数据库,各微服务分别连各自的数据库;
   ③、修改 8002/8003 各自的 yml:修改端口和数据库连接,对外暴露的统一的服务实例名不能修改;
  
   ④、启动3个 eureka 集群配置区;
   ⑤、启动3个 Dept 微服务并各自测试通过;
   ⑥、启动 microservicecloud-consumer-dept-80;
   ⑦、客户端通过 Ribbo 完成负载均衡并访问上一步的 Dept微服务;
总结:Ribbo 其实是一个软负载均衡的客户端组件,它可以和其它请求的客户端结合使用,获取 Eureka 中一个实例;

四、Ribbon 核心组件 IRule


Irule:根据特定算法从服务中选取一个要访问的服务。
【1】RoundRobinRule:轮询;
【2】RandomRule:随机;
【3】AvaliabilityFilteringRule:会先过滤掉由于多次访问故障而处于熔断器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问;
【4】WeightedResponseTimeRule:根据平均响应时间计算所有服务的权重,响应时间越快权重越大被选中的概率就越高,刚启动时统计信息不足时,则使用 RoundRobinRule 策略,等统计信息足够,则会切换到 WeightedResponseTimeRule;
【5】RetryRule:先按照 roundRobinRule 的策略获取服务,如果获取失败则会在指定时间内进行重试,获取可用服务;
【6】BestAvailableRule:会过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务;
【7】ZoneAvoidanceRule:默认规则,复合判断 server 所在区域的性能和 server 的可用性选择器;

  以上算法要使用则需要在 customer 端得 configBean.java 配置文件中添加(重要):

@Bean
public IRule myRule()
{
      //return new RoundRobinRule();
      //return new RandomRule();//达到的目的,用我们重新选择的随机算法替代默认的轮询。
      return new RetryRule();
}

五、Ribbon 自定义


【1】修改 microservicecloud-consumer-dept-80;
【2】主启动类添加注解 @RibbonClient:在启动该微服务的时候就能去加载我们的自定义 Ribbon 配置类,从而使配置生效,如:@RibbonClient(name = "MICROSERVICECLOUD-DEPT",configuration=MySelfRule.class)

1 @RibbonClient(name="MICROSERVICECLOUD-DEPT",configuration=MySelfRule.class)
2 public class DeptConsumer80_App
3 {

【3】警告:这个配置类不能放在 @ComponentScan(主启动Java文件的包下面)所扫描的当前包以及子包下,否则我们自定义的这个配置类就会被所有的 Ribbon 客户端共享,也就是说我们达不到特殊化定制的目的。自定义 myrule:(不能和主启动类同一个 package)。不同之处就在于这个类不会扫描到,而是通过 @RibbonClient 注解将其与固定的服务进行绑定。达到与其他微服务不一样的负载均衡算法的目的。

 1 @Configuration
 2 public class MySelfRule
 3 {
 4      @Bean
 5      public IRule myRule()
 6      {
 7           //return new RandomRule();// Ribbon默认是轮询,我自定义为随机
 8       //return new RoundRobinRule();// Ribbon默认是轮询,我自定义为随机
 9       return new RandomRule_ZY();// 我自定义为每台机器5次
10      }
11 }

【4】深度解析:问题:依旧是轮询策略,但是新需求,每个服务器要求被调用5次。即以前是每台机器一次,现在5次。对随机算法源码进行了修改。继承 AbstractLoadBalancerRule 实现负载均衡算法。详细学习,可以参考 GitHub 上 Ribbon 源码。

 1 public class RandomRule_ZY extends AbstractLoadBalancerRule {
 2     // total = 0 // 当total==5以后,我们指针才能往下走,
 3     // index = 0 // 当前对外提供服务的服务器地址,
 4     // total需要重新置为零,但是已经达到过一个5次,我们的index = 1
 5     // 分析:我们5次,但是微服务只有8001 8002 8003 三台,OK?
 6     private int total = 0;// 总共被调用的次数,目前要求每台被调用5次
 7     private int currentIndex = 0; // 当前提供服务的机器号
 8 
 9     public Server choose(ILoadBalancer lb, Object key) {
10         if (lb == null) {
11             return null;
12         }
13         Server server = null;
14 
15         while (server == null) {
16             if (Thread.interrupted()) {
17                 return null;
18             }
19             List<Server> upList = lb.getReachableServers();
20             List<Server> allList = lb.getAllServers();
21 
22             int serverCount = allList.size();
23             if (serverCount == 0) {
24                 /*
25                  * 没有服务器。结束而不管通过,因为后续的通过只会得到更多
26                  * 限制性的。
27                  */
28                 return null;
29             }
30 
31 //            int index = rand.nextInt(serverCount);// java.util.Random().nextInt(3);
32 //            server = upList.get(index);
33 
34 //            private int total = 0;             // 总共被调用的次数,目前要求每台被调用5次
35 //            private int currentIndex = 0;    // 当前提供服务的机器号
36             if (total < 5) {
37                 server = upList.get(currentIndex);
38                 total++;
39             } else {
40                 total = 0;
41                 currentIndex++;
42                 if (currentIndex >= upList.size()) {
43                     currentIndex = 0;
44                 }
45             }
46 
47             if (server == null) {
48                 /*
49                  * 只有当服务器列表以某种方式被修剪时,才会发生这种情况。
50                  * 这是暂时的情况。屈服后重试。
51                  */
52                 Thread.yield();
53                 continue;
54             }
55 
56             if (server.isAlive()) {
57                 return (server);
58             }
59 
60             // 不应该真的发生。。但必须是暂时的或是一个错误。
61             server = null;
62             Thread.yield();
63         }
64         return server;
65     }
66 
67     public Server choose(Object key) {
68         return choose(getLoadBalancer(), key);
69     }
70 
71     public void initWithNiwsConfig(IClientConfig clientConfig) {
72         // TODO Auto-generated method stub
73     }
74 }
posted @ 2020-11-20 09:20  Java程序员进阶  阅读(41)  评论(0编辑  收藏  举报