Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡、服务调用的工具。
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
负载均衡
简单的说就是把用户的请求平摊的多个服务上从而实现系统的HA(高可用)常见的 负载均衡的软件Nginx 硬件F5、LVS
分为集中式的LB和进程内的LB。
集中式LB:
在服务的消费方和提供方之间使用独立的LB(这个设施可以是软件层面的也可以是硬件层面的),由该设施把请求通过某种策略发送给服务的提供方。
想喝奶茶了
- 你只打一个统一的总机号码(Nginx 地址)
- 总机接线员(Nginx)帮你看店铺列表,由接线员帮你挑选一家奶茶店,再帮你转接过去
- 你不知道背后有几家店,选择权在中间这个 Nginx 服务器上
进程内LB:
将 LB 逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
想喝奶茶了
- 你(消费者)先去通讯录(注册中心)查:能做奶茶的店铺有哪些 → 拿到一堆地址:A 店、B 店
- 店铺列表直接拿回你自己手机里存着(缓存到当前服务的 JVM 内存)
- 轮到下单的时候,由你自己按照规则(轮询 / 随机)挑一家店下单
- 你直接打电话给选中的那家奶茶店,直接发起请求
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,所以必须另起炉灶

- 新建 package ------ com.atguigu.myrule
- 上面包下新建 MySelfRule 规则类
- 主启动类添加 @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下一次是哪一个服务提供服务
流程如下
- 判空:lb 为 null → 打日志,直接返回 null
- 循环最多尝试 10 次 (count <10),防止死循环:
- 获取两份列表:
reachableServers:当前可达、健康上线的服务allServers:注册中心里全部服务实例(含不健康的)
- 校验:健康数、总实例数都不能为 0,否则告警返回 null
- 调用
incrementAndGetModulo(serverCount)拿到轮询下标 - 根据下标从 allServers 取出 server
- 判断
server.isAlive() && server.isReadyToServe()✅ 满足:直接返回这个服务节点,本次负载结束 ❌ 不满足:server 置 null,进入下一轮循环重试
- 获取两份列表:
- 循环满 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高级合集内文章)+自旋锁(这里是自旋循环)