Ribbon负载均衡原理(Irule组件、源码分析)

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡、服务调用的工具。


简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。


负载均衡

简单的说就是把用户的请求平摊的多个服务上从而实现系统的HA(高可用)常见的 负载均衡的软件Nginx 硬件F5、LVS

分为集中式的LB和进程内的LB。

集中式LB:

在服务的消费方和提供方之间使用独立的LB(这个设施可以是软件层面的也可以是硬件层面的),由该设施把请求通过某种策略发送给服务的提供方。

想喝奶茶了

  1. 你只打一个统一的总机号码(Nginx 地址)
  2. 总机接线员(Nginx)帮你看店铺列表,由接线员帮你挑选一家奶茶店,再帮你转接过去
  3. 你不知道背后有几家店,选择权在中间这个 Nginx 服务器上

进程内LB:

将 LB 逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。

想喝奶茶了

  1. 你(消费者)先去通讯录(注册中心)查:能做奶茶的店铺有哪些 → 拿到一堆地址:A 店、B 店
  2. 店铺列表直接拿回你自己手机里存着(缓存到当前服务的 JVM 内存)
  3. 轮到下单的时候,由你自己按照规则(轮询 / 随机)挑一家店下单
  4. 你直接打电话给选中的那家奶茶店,直接发起请求

Ribbon 就属于进程内 LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。在调用微服务接口的时候,会在注册中心上获取注册信息服务列表后缓存在JVM本地,从而在本地实现RPC的远程调用。

ps:

ribbon和Eureka的结合只是其中的一个示例,可以和很多其他的客户端软件进行结合。

eureka的依赖和ribbon进行了整合,引入spring-starter-netflix-eureka-client依赖就可以了。

Ribbon负载均衡运行演示

Ribbon 在工作时分成两步

第一步先选择 EurekaServer , 它优先选择在同一个区域内负载较少的 server.

第二步再根据用户指定的策略,在从 server 取到的服务注册列表中选择一个地址。 其中 Ribbon 提供了多种策略:比如轮询、随机和根据响应时间加权。

Ribbon核心组件IRule

Irule:根据特定算法在服务列表中选择一个要访问的服务。

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

如何对已有的配置算法进行替换

注意⚠️:

@SpringBootApplication注解天生就带着componentScan,所以必须另起炉灶

  1. 新建 package ------ com.atguigu.myrule
  2. 上面包下新建 MySelfRule 规则类
  3. 主启动类添加 @RibbonClient
复制代码
@Configuration
public class MyselfRule {

    @Bean
    public IRule myrule(){
        return new RandomRule();//定义为随机
    }
}

Ribbon的负载均衡算法

默认规则RoundRobinRule

定位光标在Myrule接口上 快捷键crtl+alt+b可查看其实现类

Ribbon默认负载均衡策略,轮询从服务列表选节点 ,继承 AbstractLoadBalancerRule,实现 IRule。其次不是只拿健康节点列表轮询,是拿 allServers**(全部实例)** 做下标,再校验实例是否存活。lb代表当前服务器

复制代码
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        } else {
            Server server = null;
            int count = 0;

            while(true) {
                if (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) {
                        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;
            }
        }
    }

获取可达的服务,例如eureka里面的UP数。nextServerIndex下一次是哪一个服务提供服务

流程如下

  1. 判空:lb 为 null → 打日志,直接返回 null
  2. 循环最多尝试 10 次 (count <10),防止死循环:
    • 获取两份列表:
      • reachableServers:当前可达、健康上线的服务
      • allServers:注册中心里全部服务实例(含不健康的)
    • 校验:健康数、总实例数都不能为 0,否则告警返回 null
    • 调用 incrementAndGetModulo(serverCount) 拿到轮询下标
    • 根据下标从 allServers 取出 server
    • 判断 server.isAlive() && server.isReadyToServe() ✅ 满足:直接返回这个服务节点,本次负载结束 ❌ 不满足:server 置 null,进入下一轮循环重试
  3. 循环满 10 次依然找不到可用节点:告警日志,返回 null

这里一个很容易踩坑的点:下标是对 allServers 取模,不是 reachableServers 举例:一共 3 台实例,其中第 2 台宕机 下标顺序:0 →1 →2 →0 →1... 轮到下标 1 的时候拿到宕机实例,丢弃,继续重试,而不是自动跳过它、在健康列表里轮询。

复制代码
    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;
    }

incrementAndGetModulo方法就是上述图解中的负载均衡算法原理图,也叫原子轮询计数器。底层使用CAS(CompareAndSet,见JAVA.util.concurrent高级合集内文章)+自旋锁(这里是自旋循环)

相关推荐
慢云智慧空间1 小时前
从设备智能到空间理解,空间智能终端解决方案商如何重新定义行业标准
后端
利威尔xu1 小时前
Go IM 源码实战(13):30 个会话只有 2 个红点,未读数错在三条路径
redis·后端·微服务·面试·golang
薛定谔的猫-菜鸟程序员2 小时前
软件架构设计师开发资料大全(从入门到精通)
后端·python·系统架构·flask·架构设计师
IT_陈寒4 小时前
SpringBoot多环境配置把我坑惨了,这个踩坑经验你最好看看
前端·人工智能·后端
码事漫谈4 小时前
DeepSeek 搞了个 "Super-agent",但不是超级智能
后端
吴凡wudianmei4 小时前
从 0 搭一个 AI Agent(四):SSE 跑通之后,三处“契约错配”与它们的修复边界
前端·后端·aigc
小兔子4 小时前
defer 的参数在写下那一刻就求值了,循环里这样写会踩坑
后端
苍何4 小时前
腾讯 Buddy One 来了,办公生态一键打通 WorkBuddy(附教程)
后端
Tokenfree4 小时前
把通义千问接进工单系统:三个版本够用,但有两个坑没人提前告诉我
后端