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 测试修改的策略已经成功,大功告成。

      

      

  • 相关阅读:
    Android自己定义组件系列【2】——Scroller类
    ostringstream的使用方法
    什么是Spring?Spring是什么?
    天将降大任于斯人也,必先苦其心志,劳其筋骨,饿其体肤,空乏其身,行拂乱其所为,所以动心忍性,增益其所不能
    伤不起的戴尔台式机XPS8700脆弱的蓝牙
    cocos2d-x-3.1 事件分发机制 (coco2d-x 学习笔记七)
    SSL工作原理
    AfxMessageBox和MessageBox差别
    oninput,onpropertychange,onchange的使用方法和差别
    Bootstrap网站模板
  • 原文地址:https://www.cnblogs.com/javasl/p/12485522.html
Copyright © 2011-2022 走看看