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 负载均衡所有的知识点就讲解结束了。

  • 相关阅读:
    编译原理-第二章 一个简单的语法指导编译器-2.4 语法制导翻译
    编译原理-第二章 一个简单的语法指导编译器-2.3 语法定义
    编译原理-第二章 一个简单的语法指导编译器-2.2 词法分析
    LeetCode 1347. Minimum Number of Steps to Make Two Strings Anagram
    LeetCode 1348. Tweet Counts Per Frequency
    1349. Maximum Students Taking Exam(DP,状态压缩)
    LeetCode 1345. Jump Game IV(BFS)
    LeetCode 212. Word Search II
    LeetCode 188. Best Time to Buy and Sell Stock IV (动态规划)
    LeetCode 187. Repeated DNA Sequences(位运算,hash)
  • 原文地址:https://www.cnblogs.com/lusaisai/p/13063021.html
Copyright © 2011-2022 走看看