• 【云原生】springcloud09——但愿发长久,空手撕Ribbon


    在这里插入图片描述

    前 言
    🍉 作者简介:半旧518,长跑型选手,立志坚持写10年博客,专注于java后端
    ☕专栏简介:深入、全面、系统的介绍springcloud与springcloud Alibaba微服务常用技术栈
    🌰 文章简介:本文将介绍Ribbon负载均衡的原理,深入源码进行分析,并且手撕轮询算法,建议收藏备用,创作不易,敬请三连哦
    🥒文章推荐:
    微服务架构与springcloud 01——微服务入门
    微服务架构与springcloud02——父工程构建及支付模块实现
    微服务架构与springcloud03——项目热部署与消费者订单模块
    微服务架构与springcloud04——Eureka服务注册与发现
    springcloud05——Zookeeper实现支付微服务
    【云原生】springcloud06——订单服务注册zookeeper
    【云原生】springcloud07—Consul的服务注册与发现
    【云原生】springcloud08——Ribbon负载均衡调用

    1.Ribbon默认轮询算法原理

    先将注解@RibbonClient注释掉。让它恢复到最开始的轮询算法。
    在这里插入图片描述
    轮询算法的原理如下。妙不妙?
    在这里插入图片描述

    2.RoundRobinRule源码解读

    我们先解读下RoundRobinRule轮询算法的源码实现,方便后面仿照轮询算法实现默认的负载均衡算法。
    在这里插入图片描述

    先看接口IRule

    public interface IRule {
        Server choose(Object var1);
    
        void setLoadBalancer(ILoadBalancer var1);
    
        ILoadBalancer getLoadBalancer();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    里面有一个choose方法,看看在RoundRobinRule中的具体实现吧。

      public Server choose(Object key) {
            return this.choose(this.getLoadBalancer(), key);
        }
        
      public Server choose(ILoadBalancer lb, Object key) {
      // 如果没有负载均衡算法,返回null
            if (lb == null) {
                log.warn("no load balancer");
                return null;
            } else {
                Server server = null;
                int count = 0;
    
                while(true) {
                    if (server == null && count++ < 10) {
                    //获取状态为up(活着的)服务器
                        List<Server> reachableServers = lb.getReachableServers();
                        List<Server> allServers = lb.getAllServers();
                        int upCount = reachableServers.size();
                        int serverCount = allServers.size();
                        if (upCount != 0 && serverCount != 0) {
                            int nextServerIndex = this.incrementAndGetModulo(serverCount);
                            server = (Server)allServers.get(nextServerIndex);
                            if (server == null) {
                                Thread.yield();
                            } else {
                                if (server.isAlive() && server.isReadyToServe()) {
                                    return server;
                                }
    
                                server = null;
                            }
                            continue;
                        }
    
                        log.warn("No up servers available from load balancer: " + lb);
                        return null;
                    }
    
                    if (count >= 10) {
                        log.warn("No available alive servers after 10 tries from load balancer: " + lb);
                    }
    
                    return server;
                }
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    看看incrementAndGetModulo方法

      private int incrementAndGetModulo(int modulo) {
            int current;
            int next;
            do {
                current = this.nextServerCyclicCounter.get();
                next = (current + 1) % modulo;
            } while(!this.nextServerCyclicCounter.compareAndSet(current, next));
    
            return next;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.手写轮询算法

    3.1 8001和8002微服务改造

    在8001和8002的PaymentController中加上这个方法,用于测试我们的自定义轮询:

    
    @GetMapping("/lb")
    public String getPaymentLB(){
        return serverPort;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.2 订单微服务改造

    将订单微服务的负载均衡注解去掉
    在这里插入图片描述

    在springcloud包下新建lb.ILoadBalancer接口(自定义负载均衡机制(面向接口))

    public interface LoadBalancer {
    
        // 传入具体的服务集合,返回服务实例
        ServiceInstance instances(List<ServiceInstance> instances);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在lb包下新建自定义ILoadBalancer接口的实现类,实现负载均衡的核心逻辑。下面用到了CAS自旋锁的知识,让代码很健壮。

    @Component
    public class MyLB implements LoadBalancer {
        // 新建一个原子整形实例,记录访问次数,使线程安全
        private AtomicInteger visitCount = new AtomicInteger(0);
    
        public final int getAndIncrement() {
            int current;
            int next;
            do {
                current = visitCount.get();
                //如果current是最大值,重新计算,否则加1(防止越界),
                // 正常情况肯定不会出现越界的情况,但是我们可以学习源码这种方式,提升代码健壮性
                next = current >= Integer.MAX_VALUE ? 0 : current + 1;
                // 当visitCount与current相等时,说明cas成功将visitCount更新为next,终止循环
                // 当visitCount与current不相等时,说明有其他线程操作atomicInteger,返回true,取反为false,循环操作
            } while (!this.visitCount.compareAndSet(current, next));
            System.out.println("****访问次数:" + next);
            // 返回的next即visitCount自增成功后的值
            return next;
        }
    
        @Override
        public ServiceInstance instances(List<ServiceInstance> instances) {
            // 轮询算法
            int index = getAndIncrement() % instances.size();
            return instances.get(index);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    接着在我们的OrderController代码逻辑里来引入自己的自旋锁吧。

    
        @Resource
        private ILoadBalancer iLoadBalancer;
        @Resource
        private DiscoveryClient discoveryClient;
    	
        @GetMapping("/payment/lb")
        public String getPaymentLB(){
            List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
    
            //判断服务有效
            if (instances ==null || instances.size() <=0){
                return null;
            }
            ServiceInstance serviceInstance = loadBalancer.instances(instances);
    
            URI uri = serviceInstance.getUri();
            System.out.println(uri);
    
            return restTemplate.getForObject(uri+"/payment/lb",String.class);
    
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    3.3 测试

    启动Eureka Server集群7001,7002,支付微服务集群8001,8002,订单80微服务。

    在这里插入图片描述

  • 相关阅读:
    cannot find -lmysqlclient 错误解决
    多模态模型和大型语言模型(LLM):概念解析与实例探究
    什么是3D摄影机,与普通摄影机有什么不同?
    Flask 使用 JWT(三)flask-jwt-extended
    AWS DAS认证考点整理(EMR QuickSight Lakeformation等)
    9月16日,每日信息差
    Flink on yarn 加载失败plugins失效问题解决
    电工三级证(高级)实战项目:PLC控制步进电机正反转
    SPSS学习
    元数据管理Apache Atlas编译集成部署及测试
  • 原文地址:https://blog.csdn.net/qq_41708993/article/details/126594746