zoukankan      html  css  js  c++  java
  • (04)SpringCloud实战之Ribbon负载均衡

      一、负载均衡概述

      1、Ribbon 简介

      Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。

      2、负载均衡简介

      LB,即负载均衡(Load Balance),在微服务或分布式集群中经常用的一种应用。负载均衡简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA。常见的负载均衡有软件Nginx,LVS,硬件 F5等。相应的在中间件,例如:dubbo和SpringCloud中均给我们提供了负载均衡,SpringCloud的负载均衡算法可以自定义。

      (1)集中式LB:

      在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 该设施负责把访问请求通过某种策略转发至服务的提供方;

      (2)进程内LB:

      将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

      二、Ribbon配置初步

      1、修改微服务 microservicecloud-consumer-dept-80

      pom.xml 添加 Ribbon 相关依赖

    <!-- Ribbon相关 -->
       <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-eureka</artifactId>
       </dependency>
       <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-ribbon</artifactId>
       </dependency>
       <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-config</artifactId>
       </dependency>

      application.yml 添加 eureka 的服务注册地址

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

      创建 RestTemplate 时添加 @LoadBalanced 注解,第12行为新增的

     1 package com.atguigu.springcloud.cfgbeans;
     2 
     3 import org.springframework.cloud.client.loadbalancer.LoadBalanced;
     4 import org.springframework.context.annotation.Bean;
     5 import org.springframework.context.annotation.Configuration;
     6 import org.springframework.web.client.RestTemplate;
     7  
     8 @Configuration
     9 public class ConfigBean
    10 {
    11     @Bean
    12     @LoadBalanced
    13     public RestTemplate getRestTemplate()
    14     {
    15          return new RestTemplate();
    16     }
    17 }

      80微服务启动类 DeptConsumer80_App.java 添加 @EnableEurekaClient 注解(第 8 行)

     1 package com.atguigu.springcloud;
     2  
     3 import org.springframework.boot.SpringApplication;
     4 import org.springframework.boot.autoconfigure.SpringBootApplication;
     5 import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
     6  
     7 @SpringBootApplication
     8 @EnableEurekaClient
     9 public class DeptConsumer80_App
    10 {
    11   public static void main(String[] args)
    12   {
    13    SpringApplication.run(DeptConsumer80_App.class, args);
    14   }
    15 }

      修改DeptController_Consumer客户端访问类,将 http://localhost:8001 换成微服务名 MICROSERVICECLOUD-DEPT

    //private static final String REST_URL_PREFIX = "http://localhost:8001";
      private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-DEPT";

      依次启动 7001、7002、7003、8001、80 并验证下列地址均能正常访问。

      http://localhost/consumer/dept/get/1

      http://localhost/consumer/dept/list

      http://localhost/consumer/dept/add?dname=大数据部

      注意:Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号 

      三、Ribbon负载均衡

      1、架构说明及工作步骤

      Ribbon在工作时分成两步:
      (1)先选择 EurekaServer ,它优先选择在同一个区域内负载较少的server.
      (2)再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。
      其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。

      2、参考微服务8001新建微服务8002、8003

      8001、8002、8003连各自的数据库,创建数据库的脚本如下:

     
    DROP DATABASE IF EXISTS cloudDB02;
     
    CREATE DATABASE cloudDB02 CHARACTER SET UTF8;
     
    USE cloudDB02;
     
    CREATE TABLE dept
    (
      deptno BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
      dname VARCHAR(60),
      db_source   VARCHAR(60)
    );
     
    INSERT INTO dept(dname,db_source) VALUES('开发部',DATABASE());
    INSERT INTO dept(dname,db_source) VALUES('人事部',DATABASE());
    INSERT INTO dept(dname,db_source) VALUES('财务部',DATABASE());
    INSERT INTO dept(dname,db_source) VALUES('市场部',DATABASE());
    INSERT INTO dept(dname,db_source) VALUES('运维部',DATABASE());
     
    SELECT * FROM dept;
    View Code
    DROP DATABASE IF EXISTS cloudDB03;
     
    CREATE DATABASE cloudDB03 CHARACTER SET UTF8;
     
    USE cloudDB03;
     
     
    CREATE TABLE dept
    (
      deptno BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
      dname VARCHAR(60),
      db_source   VARCHAR(60)
    );
     
    INSERT INTO dept(dname,db_source) VALUES('开发部',DATABASE());
    INSERT INTO dept(dname,db_source) VALUES('人事部',DATABASE());
    INSERT INTO dept(dname,db_source) VALUES('财务部',DATABASE());
    INSERT INTO dept(dname,db_source) VALUES('市场部',DATABASE());
    INSERT INTO dept(dname,db_source) VALUES('运维部',DATABASE());
     
    SELECT * FROM dept;
    View Code

      8001、8002、8003不同的地方:pom.xml中artifactId;application.yml中port、datasource.url、instance-id;启动类名称。注意微服务名一样。

      3、依次启动eureka集群 7001、7002、7003 和 服务实例 8001、8002、8003,并验证,以下可以正常访问

      http://localhost:8001/dept/list

      http://localhost:8002/dept/list

      http://localhost:8003/dept/list

      4、启动80微服务,输入:http://localhost/consumer/dept/list 测试,每次访问的是不同的服务实例,依次轮询实现负载均衡

      第一次

      第二次

      第三次

      ... ... 

       总结:Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。

      四、Ribbon 负载均衡自定义策略

      1、Ribbon 默认的7种负载策略

      Ribbon核心组件是IRule,根据特定算法从服务列表中选取一个要访问的服务,Ribbon提供了7种负载策略

      (1)RoundRobinRule,轮询(默认的负载策略)

      (2)RandomRule,随机

      (3)AvailabilityFilteringRule,会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进访问

      (4)WeightedResponseTimeRule,根据平均响应时间计算所有服务的权重,响应时间越快,服务权重越大,被选中的概率越高。刚启动时如果统计信息不足,则使用 RoundRobinRule策略,等统计信息足够多,会切换到WeightedResponseTimeRule

      (5)RetryRule,先按照RoundRobinRule的策略获取服务,如果获取服务失败,则在指定的时间内进行重试,获取可用的服务

      (6)BestAvailableRule,会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务

      (7)ZoneAvoidanceRule,复合判断server所在区域的性能和server的可用性选择服务器

      2、测试微服务的其他负载均衡策略

      使用 Ribbon 提供的其他负载均衡策略,只需要修改80微服务的ConfigBean.java类,创建合适的IRule实例即可。

      输入地址 http://localhost/consumer/dept/get/1 分别测试下面两种随机策略。

      (1)测试随机策略(测试成功)

    @Bean
    public IRule myRule(){
      return new RandomRule();//随机算法
    }

      (2)测试RetryRule策略(测试失败,测试了几十次(或许次数不够?)依然会访问实例A,页面报 “Whitelabel Error Page” )

    @Bean
    public IRule myRule(){
      return new RetryRule();//所有的实例都正常就是轮询算法,假如A实例宕掉了,访问几次A实例报错后,就不会访问A实例了
    }

      3、自定义负载均衡策略

      现在实现一个这样的负载均衡:依旧轮询策略,但是加上新需求,每个服务器要求被调用5次。也即以前是每台机器一次,现在是每台机器5次。

      (1)修改80微服务 在启动类DeptConsumer80_App.java上添加注解

    @RibbonClient(name="MICROSERVICECLOUD-DEPT",configuration=MySelfRule.class)

      该注解的意思是:在启动 MICROSERVICECLOUD-DEPT(必须是大写) 微服务的时候去加载自定义的Ribbon配置类 MySelfRule,由它实现负载均衡。

      (2)新建类 com.atguigu.myrule.MySelfRule

    package com.atguigu.myrule;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import com.netflix.loadbalancer.IRule;
    import com.netflix.loadbalancer.RandomRule;
    
    @Configuration 
    public class MySelfRule{
        
     @Bean
     public IRule myRule(){
      return new RandomRule();//随机
     }
    }  

      注:官方文档明确给出了警告,这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,也就是说我们达不到特殊化定制的目的了。

      测试,此时启动微服务,发现已经采用了随机的策略了,说明配置是正确的,只需要创建一个自己的类替换掉 RandomRule 即可

      (3)创建访问策略类 RandomRule_ZY

      参照源码 https://github.com/Netflix/ribbon/blob/master/ribbon-loadbalancer/src/main/java/com/netflix/loadbalancer/RandomRule.java 修改如下

     1 package com.atguigu.myrule;
     2 
     3 import java.util.List;
     4 import java.util.concurrent.ThreadLocalRandom;
     5 
     6 import com.netflix.client.config.IClientConfig;
     7 import com.netflix.loadbalancer.AbstractLoadBalancerRule;
     8 import com.netflix.loadbalancer.ILoadBalancer;
     9 import com.netflix.loadbalancer.Server;
    10 
    11 public class RandomRule_ZY extends AbstractLoadBalancerRule {
    12 
    13     private int total = 0;//总共被调用的次数,目前要求每台被调用的5次
    14     private int currentIndex = 0;//当前提供服务的机器号
    15 
    16     public Server choose(ILoadBalancer lb, Object key) {
    17         if (lb == null) {
    18             return null;
    19         }
    20         Server server = null;
    21 
    22         while (server == null) {
    23             if (Thread.interrupted()) {
    24                 return null;
    25             }
    26             List<Server> upList = lb.getReachableServers();
    27             List<Server> allList = lb.getAllServers();
    28 
    29             int serverCount = allList.size();
    30             if (serverCount == 0) {
    31                 /*
    32                  * No servers. End regardless of pass, because subsequent passes
    33                  * only get more restrictive.
    34                  */
    35                 return null;
    36             }
    37 
    38            if(total < 5){
    39              server = upList.get(currentIndex);
    40              total ++;
    41            }else{
    42               total = 0;
    43               currentIndex++;
    44               if(currentIndex >= upList.size()){
    45                currentIndex = 0;
    46               }
    47            }
    48 
    49             if (server == null) {
    50                 /*
    51                  * The only time this should happen is if the server list were
    52                  * somehow trimmed. This is a transient condition. Retry after
    53                  * yielding.
    54                  */
    55                 Thread.yield();
    56                 continue;
    57             }
    58 
    59             if (server.isAlive()) {
    60                 return (server);
    61             }
    62 
    63             // Shouldn't actually happen.. but must be transient or a bug.
    64             server = null;
    65             Thread.yield();
    66         }
    67 
    68         return server;
    69 
    70     }
    71 
    72     protected int chooseRandomInt(int serverCount) {
    73         return ThreadLocalRandom.current().nextInt(serverCount);
    74     }
    75 
    76     @Override
    77     public Server choose(Object key) {
    78         return choose(getLoadBalancer(), key);
    79     }
    80 
    81     @Override
    82     public void initWithNiwsConfig(IClientConfig clientConfig) {
    83         // TODO Auto-generated method stub
    84     }
    85 }
    View Code

      注意第38-47行是添加的逻辑

      启动微服务访问 http://localhost/consumer/dept/get/1 测试修改的策略已经成功,大功告成。

      

      

  • 相关阅读:
    out/host/linuxx86/obj/EXECUTABLES/aapt_intermediates/aapt 64 32 操作系统
    linux 查看路由器 电脑主机 端口号 占用
    linux proc进程 pid stat statm status id 目录 解析 内存使用
    linux vim 设置大全详解
    ubuntu subclipse svn no libsvnjavahl1 in java.library.path no svnjavahl1 in java.library.path no s
    win7 安装 ubuntu 双系统 详解 easybcd 工具 不能进入 ubuntu 界面
    Atitit.json xml 序列化循环引用解决方案json
    Atitit.编程语言and 自然语言的比较and 编程语言未来的发展
    Atitit.跨语言  文件夹与文件的io操作集合  草案
    Atitit.atijson 类库的新特性设计与实现 v3 q31
  • 原文地址:https://www.cnblogs.com/javasl/p/12485522.html
Copyright © 2011-2022 走看看