zoukankan      html  css  js  c++  java
  • SpringCloud之Ribbon负载均衡

    此文章很大部分转载https://www.mrhelloworld.com/,博主均测试通过

    什么是 Ribbon

      Ribbon 是一个基于 HTTP 和 TCP 的 客服端负载均衡工具,它是基于 Netflix Ribbon 实现的。

      它不像 Spring Cloud 服务注册中心、配置中心、API 网关那样独立部署,但是它几乎存在于每个 Spring Cloud 微服务中。包括 Feign 提供的声明式服务调用也是基于该 Ribbon 实现的。

      Ribbon 默认提供很多种负载均衡算法,例如轮询、随机等等。甚至包含自定义的负载均衡算法。

    Ribbon 解决了什么问题

      Ribbon 提供了一套微服务的负载均衡解决方案。

    负载均衡不同方案的区别

      目前业界主流的负载均衡方案可分成两类:

    • 集中式负载均衡(服务器负载均衡),即在 consumer 和 provider 之间使用独立的负载均衡设施(可以是硬件,如 F5,也可以是软件,如 nginx),由该设施负责把访问请求通过某种策略转发至 provider;
    • 进程内负载均衡(客户端负载均衡),将负载均衡逻辑集成到 consumer,consumer 从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的 provider。Ribbon 属于后者,它只是一个类库,集成于 consumer 进程,consumer 通过它来获取 provider 的地址。

    Ribbon 负载均衡策略

      策略对应类名:RoundRobinRule

      实现原理:轮询策略表示每次都顺序取下一个 provider,比如一共有 5 个 provider,第 1 次取第 1 个,第 2 次取第 2 个,第 3 次取第 3 个,以此类推。

      策略对应类名:WeightedResponseTimeRule

      实现原理:

    • 根据每个 provider 的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性越低。
    • 原理:一开始为轮询策略,并开启一个计时器,每 30 秒收集一次每个 provider 的平均响应时间,当信息足够时,给每个 provider 附上一个权重,并按权重随机选择 provider,高权越重的 provider 会被高概率选中。

      策略对应类名:RandomRule

      实现原理:从 provider 列表中随机选择一个。

      策略对应类名:BestAvailableRule

      实现原理:选择正在请求中的并发数最小的 provider,除非这个 provider 在熔断中。

      策略对应类名:RetryRule

      实现原理:其实就是轮询策略的增强版,轮询策略服务不可用时不做处理,重试策略服务不可用时会重新尝试集群中的其他节点。

      策略对应类名:AvailabilityFilteringRule

      实现原理:过滤性能差的 provider

    • 第一种:过滤掉在 Eureka 中处于一直连接失败的 provider。
    • 第二种:过滤掉高并发(繁忙)的 provider。

      策略对应类名:ZoneAvoidanceRule

      实现原理:

    • 以一个区域为单位考察可用性,对于不可用的区域整个丢弃,从剩下区域中选可用的 provider。
    • 如果这个 ip 区域内有一个或多个实例不可达或响应变慢,都会降低该 ip 区域内其他 ip 被选中的权 重。

      eureka-demo 聚合工程。SpringBoot 2.2.4.RELEASESpring Cloud Hoxton.SR1

      Ribbon 中对于集群的服务采用的负载均衡策略默认是轮询。

    使用学习 Eureka 时的 eureka-demo 项目,在该项目中创建子项目 service-provider02

    <?xml version="1.0" encoding="UTF-8"?>
    
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.example</groupId>
        <artifactId>service-provider02</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <!-- 继承父依赖 -->
        <parent>
            <groupId>com.example</groupId>
            <artifactId>eureka-demo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </parent>
    
        <!-- 项目依赖 -->
        <dependencies>
            <!-- netflix eureka client 依赖 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <!-- spring boot web 依赖 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!-- lombok 依赖 -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <scope>provided</scope>
            </dependency>
            <!-- spring boot actuator 依赖 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
    
            <!-- spring boot test 依赖 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
      
    </project>
    server:
      port: 7071 # 端口
    
    spring:
      application:
        name: service-provider # 应用名称(集群下相同)
    
    # 配置 Eureka Server 注册中心
    eureka:
      instance:
        prefer-ip-address: true       # 是否使用 ip 地址注册
        instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
      client:
        service-url:                  # 设置服务注册中心地址
          defaultZone: http://root:123456@localhost:8761/eureka/,http://root:123456@localhost:8762/eureka/
    
    # 度量指标监控与健康检查
    management:
      endpoints:
        web:
          exposure:
            include: shutdown         # 开启 shutdown 端点访问
      endpoint:
        shutdown:
          enabled: true               # 开启 shutdown 实现优雅停服

    将所有代码复制粘贴一份至 server-provider02,修改启动类名称即可.为了更直观的看到负载均衡的效果,我们在 service-consumer 项目中将服务地址打印至控制台。

    运行完整的 Eureka 环境,访问:http://localhost:8761/ 可以看到现在已经有两个服务提供者。

    多次访问:http://localhost:9090/order/1 可以看到默认使用的是轮询策略。

    Ribbon 负载均衡策略设置

     这里是ribbon负载均衡默认的实现, 由于是笔记的关系,这里不好测试,只能你们自己去测试一下了, 具体怎么 使用呢?

    在启动类或配置类中注入负载均衡策略对象。所有服务请求均使用该策略。

    @Bean
    public IRule iRule(){
    return new RoundRobinRule();
    }

    多次访问:http://localhost:9090/order/1 结果如下:

    修改配置文件指定服务的负载均衡策略。格式:服务应用名.ribbon.NFLoadBalancerRuleClassName

    # 负载均衡策略
    # service-provider 为调用的服务的名称
    service-provider:
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

    自定义负载均衡策略:

    /*
     *
     * Copyright 2013 Netflix, Inc.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     * http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     *
     */
    package com.netflix.loadbalancer;
    
    import com.netflix.client.config.IClientConfig;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * The most well known and basic load balancing strategy, i.e. Round Robin Rule.
     *
     * @author stonse
     * @author Nikos Michalakis <nikos@netflix.com>
     *
     */
    public class RoundRobinRule extends AbstractLoadBalancerRule {
    
        private AtomicInteger nextServerCyclicCounter;
        private static final boolean AVAILABLE_ONLY_SERVERS = true;
        private static final boolean ALL_SERVERS = false;
    
        private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);
    
        public RoundRobinRule() {
            nextServerCyclicCounter = new AtomicInteger(0);
        }
    
        public RoundRobinRule(ILoadBalancer lb) {
            this();
            setLoadBalancer(lb);
        }
    
        public Server choose(ILoadBalancer lb, Object key) {
            if (lb == null) {
                log.warn("no load balancer");
                return null;
            }
    
            Server server = null;
            int count = 0;
            while (server == null && count++ < 10) {
                List<Server> reachableServers = lb.getReachableServers();
                List<Server> allServers = lb.getAllServers();
                int upCount = reachableServers.size();
                int serverCount = allServers.size();
    
                if ((upCount == 0) || (serverCount == 0)) {
                    log.warn("No up servers available from load balancer: " + lb);
                    return null;
                }
    
                int nextServerIndex = incrementAndGetModulo(serverCount);
                server = allServers.get(nextServerIndex);
    
                if (server == null) {
                    /* Transient. */
                    Thread.yield();
                    continue;
                }
    
                if (server.isAlive() && (server.isReadyToServe())) {
                    return (server);
                }
    
                // Next.
                server = null;
            }
    
            if (count >= 10) {
                log.warn("No available alive servers after 10 tries from load balancer: "
                        + lb);
            }
            return server;
        }
    
        /**
         * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
         *
         * @param modulo The modulo to bound the value of the counter.
         * @return The next value.
         */
        private int incrementAndGetModulo(int modulo) {
            for (;;) {
                int current = nextServerCyclicCounter.get();
                int next = (current + 1) % modulo;
                if (nextServerCyclicCounter.compareAndSet(current, next))
                    return next;
            }
        }
    
        @Override
        public Server choose(Object key) {
            return choose(getLoadBalancer(), key);
        }
    
        @Override
        public void initWithNiwsConfig(IClientConfig clientConfig) {
        }
    }

    我们来看看这个类AbstractLoadBalancerRule

    /**
     * Class that provides a default implementation for setting and getting load balancer
     * @author stonse
     *
     */
    public abstract class AbstractLoadBalancerRule implements IRule, IClientConfigAware {
    
        private ILoadBalancer lb;
            
        @Override
        public void setLoadBalancer(ILoadBalancer lb){
            this.lb = lb;
        }
        
        @Override
        public ILoadBalancer getLoadBalancer(){
            return lb;
        }      
    }

    这里我们能发现,还是我们上面所说过的 实现了IRule就能够自定义负载均衡即使是他默认的策略也实现了IRule 我们可以直接把代码copy过来改动一点

    import com.netflix.client.config.IClientConfig;
    import com.netflix.loadbalancer.AbstractLoadBalancerRule;
    import com.netflix.loadbalancer.ILoadBalancer;
    import com.netflix.loadbalancer.Server;
    import java.util.List;
    import java.util.Random;
    
    public class LubanRule extends AbstractLoadBalancerRule {
        //原来是纯随机策略 我们现在改为。 如果一个下标已经被随机到了2次了,第三次还是同样的下标的话,那就再随机一次
        Random rand;
        private int lastIndex = -1;
        private int nowIndex = -1;
        private int skipIndex = -1;
        public LubanRule() {
            rand = new Random();
        }
        /**
         * Randomly choose from all living servers
         */
        public Server choose(ILoadBalancer lb, Object key) {
            if (lb == null) {
                return null;
            }
            Server server = null;
            while (server == null) {
                if (Thread.interrupted()) {
                    return null;
                }
                List<Server> upList = lb.getReachableServers();
                List<Server> allList = lb.getAllServers();
                int serverCount = allList.size();
                if (serverCount == 0) {
                    /*
                     * No servers. End regardless of pass, because subsequent passes
                     * only get more restrictive.
                     */
                    return null;
                }
                int index = rand.nextInt(serverCount);
                System.out.println("当前下标为:"+index);
                if (skipIndex>=0&&index == skipIndex) {
                    System.out.println("跳过");
                    index = rand.nextInt(serverCount);
                    System.out.println("跳过后的下标:"+index);
                }
                skipIndex=-1;
                nowIndex = index;
                if (nowIndex == lastIndex) {
                    System.out.println("下一次需要跳过的下标"+nowIndex);
                    skipIndex = nowIndex;
                }
                lastIndex = nowIndex;
                server = upList.get(index);
                if (server == null) {
                    /*
                     * The only time this should happen is if the server list were
                     * somehow trimmed. This is a transient condition. Retry after
                     * yielding.
                     */
                    Thread.yield();
                    continue;
                }
                if (server.isAlive()) {
                    return (server);
                }
                // Shouldn't actually happen.. but must be transient or a bug.
                server = null;
                Thread.yield();
            }
            return server;
        }
    
        @Override
        public void initWithNiwsConfig(IClientConfig clientConfig) {
    
        }
    
        @Override
        public Server choose(Object key) {
            return choose(getLoadBalancer(), key);
        }
    }

    这里我们就把自己写的Rule给new出来交给spring 就好了

    @Bean
    public IRule iRule(){
       return new LubanRule();
    }

    具体测试的话就不测试了, 那个效果放在笔记上不太明显,可以自己把代码copy过去测试一下

    Ribbon 点对点直连

    点对点直连是指绕过注册中心,直接连接服务提供者获取服务,一般在测试阶段使用比较多。

    在调用方 pom 文件中引入 ribbon 依赖,需要注意的是如果 pom 中有 Eureka 的依赖,则需要去除 Eureka 的依赖。

    <!-- netflix ribbon 依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>

    配置文件中关闭 Eureka,添加直连的服务地址。如果不设置负载均衡策略默认使用轮询策略。

    # 负载均衡策略
    # service-provider 为调用的服务的名称
    service-provider:
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
        # 点对点直连模式,指定具体的 Provider 服务列表,多个用逗号隔开
        listOfServers: http://localhost:7070,http://localhost:7071
    
    # 关闭 Eureka 实现 Ribbon 点对点直连
    ribbon:
      eureka:
        enabled: false # false:关闭,true:开启

    关闭 Eureka 注册中心,服务提供者由于无法连接至注册中心所以会报连接异常。但是服务是可以正常可消费的,所以目前使用的是点对点的方式来进行调用的。

    多次访问:http://localhost:9090/order/1 结果如下:

     至此 Ribbon 负载均衡所有的知识点就讲解结束了。

  • 相关阅读:
    十、Compose命令
    九、compose入门
    JS数组中,两两比较的算法,为了获取重复的值,并在php数组中,使用这种倒三角算法
    题目重复度检测---四种相似度检测的方案+PHP改进计算字符串相似度的函数similar_text()、levenshtein()
    js让文字一个个出现
    客户总想让页面炫酷起来-----怎么炫酷呢?---使用css3的东西或者其animate.css
    关于git的总结:git知识大全,命令行操作,vscode操作
    defer和async的区别
    dom和bom的区别,以及三类偏移属性(JS运动方法中,经常会涉及到这些位置信息)
    认真的对待flex,Flex模型和Flex属性思维导图,以及自己的记忆方法--jaf和aof
  • 原文地址:https://www.cnblogs.com/lusaisai/p/13063021.html
Copyright © 2011-2022 走看看