• SpringCloud-Ribbon负载均衡机制、手写轮询算法


    Ribbon 内置的负载均衡规则

    com.netflix.loadbalancer 包下有一个接口 IRule,它可以根据特定的算法从服务列表中选取一个要访问的服务,默认使用的是「轮询机制」

    • RoundRobinRule:轮询
    • RandomRule:随机
    • RetryRule:先按照 RoundRobinRule 的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务
    • WeightedResponseTimeRule:对 RoundRobinRule 的扩展,响应速度越快的实例选择权重越大,越容易被选择
    • BestAvailableRule:会过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
    • AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例
    • ZoneAvoidanceRule:默认规则,复合判断 server 所在区域的性能和 server 的可用性选择服务器

    负载规则的替换

    如果不想使用 Ribbon 默认使用的规则,我们可以通过自定义配置类的方式,手动指定使用哪一种。

    需要注意的是,自定义配置类不能放在 @ComponentScan 所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的 Ribbon 客户端所共享,达不到特殊化定制的目的了。

    因此我们需要在 Spring Boot 启动类所在包的外面新建一个包存放自定义配置类

    @Configuration
    public class MyselfRule {
        
        @Bean
        public IRule rule(){
            //随机
            return new RandomRule();
        }
    }
    

    然后在启动类上添加如下注解,指定服务名及自定义配置类

    @RibbonClient(value = "CLOUD-PAYMENT-SERVICE", configuration = MyselfRule.class)
    

    Ribbon 默认负载轮询算法的原理

    算法概述

    rest 接口第几次请求数 % 服务器集群总个数 = 实际调用服务器位置下标,服务每次重启后 rest 请求数变为1

    源码

    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        }
    
        Server server = null;
        int count = 0;
        //循环获取服务,最多获取10次
        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;
    }
    
    //通过此方法获取服务的下标,使用了 CAS 和自旋锁
    private int incrementAndGetModulo(int modulo) {
        for (;;) {
            int current = nextServerCyclicCounter.get();
            int next = (current + 1) % modulo;
            if (nextServerCyclicCounter.compareAndSet(current, next))
                return next;
        }
    }
    

    手写轮询算法

    在服务提供者写一个方法,返回端口号看效果就行

    @GetMapping("/payment/lb")
    public String roundLb(){
        return this.serverPort;
    }
    

    负载均衡接口

    public interface LoadBalancer {
        /**
         * 获取服务实例
         */
        ServiceInstance getInstance(List<ServiceInstance>serviceInstances);
    }
    

    算法实现类

    @Component
    public class MyLb implements LoadBalancer {
    
        private AtomicInteger atomicInteger = new AtomicInteger(0);
    
        /**
         * 使用「自旋锁」和「CAS」增加请求次数
         */
        public final int incrementAndGet() {
            int current;
            int next;
            do {
                current = atomicInteger.get();
                //防溢出
                next = current >= Integer.MAX_VALUE ? 0 : current + 1;
            } while (!atomicInteger.compareAndSet(current, next));
            return next;
        }
    
        @Override
        public ServiceInstance getInstance(List<ServiceInstance> serviceInstances) {
            // 实际调用服务器位置下标 = rest 接口第几次请求数 % 服务器集群总个数
            int index = incrementAndGet() % serviceInstances.size();
            return serviceInstances.get(index);
        }
    }
    

    编写服务消费者方法,记得注释 @LoadBalanced 注解,否则不生效

    @GetMapping("/consumer/payment/lb")
    public String roundLb(){
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        if (instances == null || instances.size() <= 0){
            return null;
        }
        ServiceInstance instance = loadBalancer.getInstance(instances);
        URI uri = instance.getUri();
        return restTemplate.getForObject(uri + "/payment/lb", String.class);
    }
    
  • 相关阅读:
    企业如何推行白盒测试
    Java命名规范
    MobileVLC for iphoneos4.3
    用Android NDK编译FFmpeg
    Linux 下Android 开发环境搭建 ---CentOS
    为什么要做白盒测试
    vlc的第三方库contrib的修改之一:live库的修改
    VC命名规范
    POJ 1470 Closest Common Ancestors (LCA入门题)
    HDU 4407 Sum 第37届ACM/ICPC 金华赛区 第1008题 (容斥原理)
  • 原文地址:https://www.cnblogs.com/songjilong/p/12752115.html
Copyright © 2020-2023  润新知