Appearance
Ribbon 详解:客户端负载均衡
Ribbon 是 Netflix 开源的,曾是 Spring Cloud 生态中服务调用的标配。它嵌入在 Feign 和 RestTemplate 中,让调用方自己从注册中心获取实例列表、选择实例、发起调用,省去了中心化负载均衡器的瓶颈。
一、为什么需要客户端负载均衡?
1.1 服务端 vs 客户端负载均衡


1.2 微服务中为什么用客户端负载均衡?
微服务内部调用走的是内网,不需要经过 Nginx。客户端负载均衡让调用方自己从注册中心获取实例列表并选择,好处是:
- 无中心瓶颈:不需要所有流量经过一个 LB 节点
- 更智能的路由:客户端知道所有实例的状态,可以做最小连接数、加权响应时间等决策
- 少一跳:调用方直连目标实例,减少一次网络转发
二、Ribbon 工作原理
Ribbon 的工作流程可以概括为三个核心步骤:拦截请求 → 获取列表 → 选择实例。
调用方发起请求
│ http://order-service/api/orders
▼
┌─────────────────────────────────────────────┐
│ 步骤1:拦截请求 │
│ LoadBalancerInterceptor 拦截 RestTemplate 请求 │
│ 从 URL 中提取服务名:"order-service" │
└──────────────────┬──────────────────────────┘
▼
┌─────────────────────────────────────────────┐
│ 步骤2:获取服务列表(服务发现) │
│ Ribbon 从 Eureka/Nacos 拉取所有可用实例列表 │
│ order-service → [IP1:8080, IP2:8080, IP3:8080] │
│ 缓存到本地,每 30s 定时刷新 │
└──────────────────┬──────────────────────────┘
▼
┌─────────────────────────────────────────────┐
│ 步骤3:按策略选择一个实例(负载均衡算法) │
│ IRule 从列表中按策略选出一个实例 │
│ 默认轮询(RoundRobin),可选随机/加权/最小并发等 │
└──────────────────┬──────────────────────────┘
▼
真实请求:http://192.168.1.100:8080/api/orders详细流程

下面深入到 Ribbon 内部,看这 5 个核心组件是如何协作完成以上三步的。
2.1 五大核心组件
Ribbon 由 5 个核心组件协作完成负载均衡:
┌─────────────────────────────────┐
│ ILoadBalancer │ ← 协调器,对外提供 chooseServer()
└──────────────┬──────────────────┘
│
┌──────────────────────────┼──────────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────────┐ ┌────────────────────┐ ┌────────────────────┐
│ ServerList │ │ IRule │ │ IPing │
│ 获取实例列表 │ │ 负载均衡策略 │ │ 健康检查 │
└──────────────────┘ └────────────────────┘ └────────────────────┘
│ │
└────────────────────┬─────────────────────────────┘
▼
┌──────────────────────┐
│ ServerListFilter │ ← 过滤掉不健康的实例
└──────────────────────┘| 组件 | 接口 | 职责 |
|---|---|---|
| ServerList | ServerList<Server> | 从注册中心(Eureka/Nacos)获取服务实例列表 |
| ServerListUpdater | ServerListUpdater | 定时更新 ServerList,保证实例列表是最新的 |
| IRule | IRule | 负载均衡策略,从实例列表中选一个 |
| IPing | IPing | 检查实例是否存活,剔除不健康实例 |
| ServerListFilter | ServerListFilter<T> | 过滤实例列表(如按 Zone 过滤) |
| ILoadBalancer | ILoadBalancer | 协调上述组件,对外提供 chooseServer() 方法 |
2.2 ILoadBalancer 继承体系
这是 Ribbon 最核心的接口,定义了负载均衡器的行为:
ILoadBalancer(接口)
│
├── AbstractLoadBalancer(抽象类)
│ │
│ └── BaseLoadBalancer(基础实现)
│ │ - 维护两个列表:allServerList(全部实例)、upServerList(存活实例)
│ │ - 持有 IRule 和 IPing 实例
│ │ - chooseServer() 委托给 IRule.choose()
│ │
│ └── DynamicServerListLoadBalancer(动态实例列表)
│ │ - 持有 ServerList 和 ServerListUpdater
│ │ - 定时从 Eureka 更新实例列表
│ │ - 实例变化时触发 ServerListFilter 过滤
│ │
│ └── ZoneAwareLoadBalancer(区域感知)
│ - 多 Zone 部署时,优先选择同 Zone 实例
│ - 同 Zone 不可用时跨 Zone 选择BaseLoadBalancer 核心源码(简化):
java
public class BaseLoadBalancer extends AbstractLoadBalancer {
// 维护两个列表
protected volatile List<Server> allServerList = new ArrayList<>(); // 全部实例
protected volatile List<Server> upServerList = new ArrayList<>(); // 存活实例
protected IRule rule = new RoundRobinRule(); // 默认轮询策略
protected IPing ping = new DummyPing(); // 默认不检查
@Override
public Server chooseServer(Object key) {
// 委托给 IRule 选择
return rule.choose(key);
}
// 标记某个 Server 为不可用
public void markServerDown(Server server) {
upServerList.remove(server);
}
}2.3 ServerList — 实例列表从哪里来
ServerList 是 Ribbon 与注册中心的桥梁。Spring Cloud 中默认使用 DiscoveryEnabledNIWSServerList:
java
// 从 Eureka 获取实例列表
public class DiscoveryEnabledNIWSServerList extends AbstractServerList<DiscoveryEnabledServer> {
@Override
public List<DiscoveryEnabledServer> getInitialListOfServers() {
// 首次:从 EurekaClient 获取指定 serviceId 的所有实例
return obtainServersViaDiscovery();
}
@Override
public List<DiscoveryEnabledServer> getUpdatedListOfServers() {
// 更新:重新从 EurekaClient 拉取最新实例列表
return obtainServersViaDiscovery();
}
private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
// 1. 通过 EurekaClient 获取 Application 对象
// 2. 遍历所有 InstanceInfo,包装为 DiscoveryEnabledServer
// 3. 返回列表
}
}关键: ServerList 不负责定时刷新,刷新由 ServerListUpdater 负责。
2.4 ServerListUpdater — 定时刷新实例列表
ServerListUpdater 是一个独立的后台线程,定时从 ServerList 获取最新实例列表,更新到 ILoadBalancer 中。
java
// 默认实现:PollingServerListUpdater
public class PollingServerListUpdater implements ServerListUpdater {
// 默认 30 秒刷新一次
private static int LISTOFSERVERS_CACHE_REPEAT_INTERVAL = 30 * 1000;
@Override
public synchronized void start(final UpdateAction updateAction) {
// 启动一个 ScheduledThreadPoolExecutor 定时任务
// 每 30 秒调用 updateAction.doUpdate()
// doUpdate() 内部:
// 1. 调用 ServerList.getUpdatedListOfServers() 获取最新列表
// 2. 调用 ILoadBalancer.updateListOfServers(newList) 更新
}
}刷新流程:
每 30 秒:
PollingServerListUpdater 定时触发
→ ServerList.getUpdatedListOfServers()
→ EurekaClient.getInstancesByVipAddress("order-service")
→ 返回最新的 InstanceInfo 列表
→ ILoadBalancer.updateListOfServers(newList)
→ 更新 allServerList
→ 用 IPing 检查新实例存活状态
→ 更新 upServerList2.5 IPing — 健康检查
IPing 负责检查实例是否存活,不健康的实例从 upServerList 中移除。
| 实现类 | 策略 | 说明 |
|---|---|---|
| DummyPing | 永远返回 true | 不检查,默认 |
| NIWSDiscoveryPing | 比较 Eureka 状态 | 检查 Eureka 中实例状态是否为 UP |
| PingUrl | HTTP 请求 health 端点 | 真正的 HTTP 健康检查 |
| NoOpPing | 不做任何事 | 占位符 |
java
// NIWSDiscoveryPing:直接复用 Eureka 的健康状态
public class NIWSDiscoveryPing implements IPing {
@Override
public boolean isAlive(Server server) {
// InstanceInfo.getStatus() == InstanceStatus.UP → true
// 其他状态(DOWN/OUT_OF_SERVICE)→ false
return ((DiscoveryEnabledServer) server).getInstanceInfo().getStatus()
== InstanceStatus.UP;
}
}为什么默认用 DummyPing? 因为 Ribbon 与 Eureka 集成时,Eureka 已经做了健康检查(心跳机制),Ribbon 不需要重复检查。NIWSDiscoveryPing 直接读取 Eureka 中的状态即可。
2.6 ServerListFilter — 过滤实例
ServerListFilter 在 upServerList 基础上进一步过滤,可以根据 Zone、元数据等条件筛选。
java
// ZonePreferenceServerListFilter:按 Zone 过滤
public class ZonePreferenceServerListFilter extends ZoneAffinityServerListFilter {
@Override
public List<Server> getFilteredListOfServers(List<Server> servers) {
List<Server> filtered = super.getFilteredListOfServers(servers);
// 过滤掉不可用的 Zone 中的实例
if (isZoneAffinityAvailable(filtered)) {
return filtered; // 同 Zone 实例可用,只返回同 Zone 的
}
return servers; // 同 Zone 不可用,返回全部实例(跨 Zone)
}
}三、7 种内置负载均衡策略
3.1 IRule 接口
java
public interface IRule {
Server choose(Object key); // 选一个实例
void setLoadBalancer(ILoadBalancer lb); // 设置负载均衡器引用
ILoadBalancer getLoadBalancer();
}3.2 RoundRobinRule — 轮询(默认)
原理: 维护一个原子计数器 incrementAndGetModulo(),每次请求计数器 +1,模上实例总数,得到目标实例下标。
java
public class RoundRobinRule extends AbstractLoadBalancerRule {
private AtomicInteger nextServerCyclicCounter;
@Override
public Server choose(ILoadBalancer lb, Object key) {
List<Server> upList = lb.getReachableServers(); // 存活实例
int serverCount = upList.size();
if (serverCount == 0) return null;
// 核心:原子递增,取模
int nextServerIndex = incrementAndGetModulo(serverCount);
return upList.get(nextServerIndex);
}
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextServerCyclicCounter.get();
int next = (current + 1) % modulo; // 取模循环
if (nextServerCyclicCounter.compareAndSet(current, next)) {
return next;
}
}
}
}关键: 使用 AtomicInteger + CAS 保证线程安全,无需加锁,性能高。compareAndSet 失败则自旋重试,确保计数准确。
3.3 RandomRule — 随机
原理: 用 ThreadLocalRandom 生成随机下标,每个实例被选中的概率均等。
java
public class RandomRule extends AbstractLoadBalancerRule {
@Override
public Server choose(ILoadBalancer lb, Object key) {
List<Server> upList = lb.getReachableServers();
int index = ThreadLocalRandom.current().nextInt(upList.size());
return upList.get(index);
}
}3.4 WeightedResponseTimeRule — 加权响应时间
原理: 这是最复杂的策略,核心思想是"响应越快的实例,权重越高,分到的请求越多"。权重每 30 秒重新计算一次。
权重计算公式:
1. 收集每个实例的平均响应时间
2. 计算总响应时间 = sum(所有实例的响应时间)
3. 每个实例的权重 = 总响应时间 - 该实例的响应时间
(响应时间越短,权重越大)
4. 用累加权重区间做随机选择源码实现(简化):
java
public class WeightedResponseTimeRule extends RoundRobinRule {
// 维护每个实例的权重
private volatile List<Double> accumulatedWeights = new ArrayList<Double>();
// 每 30 秒重新计算权重
void initialize(ILoadBalancer lb) {
// 启动定时任务,每 30 秒执行 calculateWeights()
new DynamicServerWeightTask();
}
void calculateWeights() {
// 1. 收集每个实例的平均响应时间
List<Double> finalResponseTimeList = new ArrayList<>();
for (Server server : serverList) {
ServerStats stats = serverStatsCache.get(server);
finalResponseTimeList.add(stats.getResponseTimeAvg());
}
// 2. 计算总响应时间
double totalResponseTime = 0;
for (Double rt : finalResponseTimeList) {
totalResponseTime += rt;
}
// 3. 计算权重:weightSoFar + 总响应时间 - 该实例响应时间
double weightSoFar = 0.0;
List<Double> finalWeights = new ArrayList<>();
for (Double rt : finalResponseTimeList) {
double weight = totalResponseTime - rt; // 响应越快,weight 越大
weightSoFar += weight;
finalWeights.add(weightSoFar); // 累加权重
}
accumulatedWeights = finalWeights;
}
@Override
public Server choose(ILoadBalancer lb, Object key) {
// 4. 随机一个数,落在哪个权重区间就选哪个实例
double randomWeight = ThreadLocalRandom.current().nextDouble()
* accumulatedWeights.get(accumulatedWeights.size() - 1);
for (int i = 0; i < accumulatedWeights.size(); i++) {
if (accumulatedWeights.get(i) >= randomWeight) {
return serverList.get(i);
}
}
return super.choose(lb, key); // fallback 到轮询
}
}示例: 3 个实例,响应时间分别为 10ms、30ms、60ms:
总响应时间 = 100
实例A 权重 = 100 - 10 = 90 → 概率 90/210 = 43%
实例B 权重 = 100 - 30 = 70 → 概率 70/210 = 33%
实例C 权重 = 100 - 60 = 40 → 概率 40/210 = 19%
总权重 = 90 + 70 + 40 = 2103.5 BestAvailableRule — 最小并发数
原理: 遍历所有实例,选择 activeRequestsCount(活跃请求数)最小的实例。活跃请求数 = 该实例当前正在处理的请求数量。
java
public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule {
@Override
public Server choose(Object key) {
List<Server> serverList = getLoadBalancer().getAllServers();
int minimalConcurrentConnections = Integer.MAX_VALUE;
Server chosen = null;
for (Server server : serverList) {
ServerStats stats = serverStatsCache.get(server);
int concurrentConnections = stats.getActiveRequestsCount(/* 当前时间 */);
if (concurrentConnections < minimalConcurrentConnections) {
minimalConcurrentConnections = concurrentConnections;
chosen = server;
}
}
// 跳过被熔断器标记的实例
if (chosen != null && isCircuitBreakerTripped(chosen)) {
return super.choose(key); // fallback 到轮询
}
return chosen;
}
}3.6 AvailabilityFilteringRule — 可用性过滤
原理: 先过滤掉不可用的实例,再从剩余的实例中轮询选择。
过滤条件(同时满足任一即过滤):
- 熔断器打开(连续失败次数超过阈值,默认 3 次)
- 活跃连接数超过上限(默认 2^31-1,即无限制)
java
public class AvailabilityFilteringRule extends PredicateBasedRule {
@Override
public Server choose(Object key) {
// 用 AvailabilityPredicate 过滤
// 过滤掉 circuitBreakerTripped 或 activeConnectionsLimit 超限的实例
List<Server> available = getPredicate().getEligibleServers(
getLoadBalancer().getAllServers(), key);
// 从可用实例中轮询选择
return roundRobinRule.choose(available);
}
}3.7 ZoneAvoidanceRule — 区域感知
原理: Ribbon 最复杂的规则,基于 Zone(可用区)和实例可用性做复合过滤 + 选择。
选择流程:
1. 获取所有实例,按 Zone 分组
2. 过滤掉不可用的 Zone(Zone 内实例故障率 > 阈值 或 平均负载 > 阈值)
3. 优先选择同 Zone 的实例
4. 同 Zone 内用 RoundRobinRule 选择
5. 如果同 Zone 无可用实例,跨 Zone 选择Zone 可用性判断:
java
// Zone 故障率 = 该 Zone 中熔断打开的实例数 / 该 Zone 总实例数
// 默认阈值:zoneAffinity.zoneExcludePercentage = 0.5(50%)
// 如果 Zone 故障率 > 50%,标记该 Zone 为不可用
// Zone 平均负载 = 该 Zone 全部实例平均活跃请求数
// 默认阈值:activeConnectionsLimit = Integer.MAX_VALUE
// 如果 Zone 平均负载超过阈值,标记该 Zone 为不可用3.8 RetryRule — 重试
原理: 包装另一个 IRule,在指定时间内反复尝试 choose(),直到选到可用实例或超时。
java
public class RetryRule extends AbstractLoadBalancerRule {
IRule subRule = new RoundRobinRule();
long maxRetryMillis = 500; // 默认 500ms
@Override
public Server choose(ILoadBalancer lb, Object key) {
long requestTime = System.currentTimeMillis();
long deadline = requestTime + maxRetryMillis;
Server answer = null;
while (deadline >= System.currentTimeMillis() && answer == null) {
answer = subRule.choose(key);
}
return answer;
}
}3.9 策略选择建议
| 场景 | 推荐策略 | 原因 |
|---|---|---|
| 实例性能一致 | RoundRobinRule | 默认,够用,简单公平 |
| 实例性能差异大 | WeightedResponseTimeRule | 自动根据响应时间调整权重 |
| 请求处理时间差异大 | BestAvailableRule | 避免请求堆积在慢实例 |
| 多机房部署 | ZoneAvoidanceRule | 优先同机房,减少跨机房延迟 |
| 需要容错重试 | RetryRule | 自动跳过故障实例 |
| 需要过滤故障实例 | AvailabilityFilteringRule | 熔断器 + 并发限制双重过滤 |
四、核心配置
4.1 全局配置
yaml
ribbon:
ConnectTimeout: 3000 # 连接超时 3s
ReadTimeout: 5000 # 读取超时 5s
MaxAutoRetries: 1 # 当前实例重试次数
MaxAutoRetriesNextServer: 1 # 切换到下一个实例的重试次数
OkToRetryOnAllOperations: false # 只对 GET 请求重试4.2 针对特定服务配置
yaml
order-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
ConnectTimeout: 2000
ReadTimeout: 3000
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 24.3 代码配置
java
@Configuration
public class RibbonConfig {
@Bean
public IRule ribbonRule() {
return new WeightedResponseTimeRule();
}
}五、Ribbon 与 Spring Cloud 的集成原理
5.1 @LoadBalanced — RestTemplate 的魔法
在 RestTemplate 上标注 @LoadBalanced,就能自动实现负载均衡。这是怎么做到的?
原理: @LoadBalanced 是一个标记注解,配合 LoadBalancerAutoConfiguration 实现自动装配:
java
// 1. @LoadBalanced 标记注解
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Qualifier // 关键:作为 Qualifier 使用
public @interface LoadBalanced { }2. 自动装配流程:
java
@Configuration
public class LoadBalancerAutoConfiguration {
// 收集所有标注了 @LoadBalanced 的 RestTemplate Bean
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
// 为每个 @LoadBalanced RestTemplate 添加拦截器
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
final List<RestTemplateCustomizer> customizers) {
return () -> {
for (RestTemplate restTemplate : restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
};
}
// 自定义器:向 RestTemplate 中添加 LoadBalancerInterceptor
@Bean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor interceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
interceptors.add(interceptor);
};
}
}5.2 LoadBalancerInterceptor — 拦截请求
这是负载均衡的核心拦截器,拦截所有 RestTemplate 发出的 HTTP 请求:
java
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) {
// 1. 从 URL 中提取服务名
URI originalUri = request.getURI();
String serviceName = originalUri.getHost(); // "order-service"
// 2. 通过 Ribbon 选择一个实例
ServiceInstance instance = loadBalancer.choose(serviceName);
// → 内部调用 ILoadBalancer.chooseServer()
// → 返回选中的实例(IP:Port)
// 3. 用实例的 IP:Port 替换 URL 中的服务名
URI actualUri = loadBalancer.reconstructURI(instance, originalUri);
// 原始: http://order-service/api/orders
// 替换: http://192.168.1.101:8080/api/orders
// 4. 用替换后的 URL 发起真正的 HTTP 请求
return execution.execute(newRequest, body);
}
}5.3 RibbonLoadBalancerClient — 桥接 Spring Cloud 与 Ribbon
java
public class RibbonLoadBalancerClient implements LoadBalancerClient {
@Override
public ServiceInstance choose(String serviceId) {
// 1. 获取该服务对应的 ILoadBalancer
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 2. 调用 Ribbon 的 chooseServer() 选择实例
Server server = loadBalancer.chooseServer("default");
// 3. 将 Ribbon 的 Server 包装为 Spring Cloud 的 ServiceInstance
return new RibbonServer(serviceId, server, ...);
}
}5.4 Feign 与 Ribbon 的集成
Feign 通过 FeignLoadBalancer 将负载均衡委托给 Ribbon:
@FeignClient(name = "order-service")
│
▼
Feign 拦截调用 → 提取服务名 "order-service"
│
▼
FeignLoadBalancer.execute()
│
├── RibbonLoadBalancerClient.choose("order-service")
│ └── ILoadBalancer.chooseServer()
│ ├── ServerList 从 Eureka 获取实例列表
│ ├── IPing 过滤不健康实例
│ ├── ServerListFilter 按 Zone 过滤
│ └── IRule 按策略选择 → 返回 IP:Port
│
└── Feign 用选中的 IP:Port 构造 HTTP 请求,发送六、Ribbon 的重试机制
6.1 重试参数
Ribbon 的重试由两个参数控制:
MaxAutoRetries = 1 # 当前实例重试次数
MaxAutoRetriesNextServer = 2 # 切换到下一个实例的最大次数
重试路径:
请求 → 实例A(失败)→ 重试实例A(失败)→ 换实例B(失败)→ 换实例C
↑ ↑ ↑ ↑
原始请求 实例A重试1次 换实例1次 换实例2次
总共最多尝试 3 个实例,每个实例最多重试 1 次6.2 重试的底层实现
Ribbon 的重试不是在 Ribbon 层实现的,而是在 Feign 或 RestTemplate 层通过 Spring Retry 实现:
java
@Configuration
public class FeignRetryConfig {
@Bean
public Retryer feignRetryer() {
// Feign 自带的重试器
return new Retryer.Default(100, 1000, 3);
// 初始间隔 100ms,最大间隔 1s,最多重试 3 次
}
}注意: OkToRetryOnAllOperations = false 表示只对 GET 请求重试,POST 请求不重试(防止重复提交)。
七、Ribbon 为什么停维了?
2020 年,Netflix 宣布 Ribbon 进入维护模式(不再添加新功能,只修 Bug)。原因:
- 阻塞式 IO:Ribbon 基于
HttpURLConnection或Apache HttpClient,都是阻塞式 IO,线程资源占用高 - Spring Cloud 拥抱响应式:Spring Cloud 2020+ 引入 Spring Cloud LoadBalancer,基于响应式(Reactive)架构,非阻塞,性能更好
- Netflix 全线停维:不只 Ribbon,Hystrix、Zuul 等 Netflix OSS 组件也相继停维,Spring 官方决定推出自己的替代方案
现状: 新项目直接使用 Spring Cloud LoadBalancer,旧项目使用 Ribbon 也无需急于迁移,但不再推荐新项目引入。
八、面试要点
Q:Ribbon 的负载均衡原理?
通过 ILoadBalancer 协调 ServerList(获取实例列表)、ServerListUpdater(定时刷新)、IPing(健康检查)、ServerListFilter(过滤)、IRule(选择策略),从注册中心获取实例列表后,按策略选出一个实例返回给调用方。
Q:Ribbon 有哪些负载均衡策略?
轮询(RoundRobin,CAS 原子递增)、随机(Random)、加权响应时间(WeightedResponseTime,每 30 秒重新计算权重)、最小并发(BestAvailable)、可用性过滤(AvailabilityFiltering)、区域感知(ZoneAvoidance)、重试(RetryRule)。
Q:@LoadBalanced 注解的原理?
@LoadBalanced 是标记注解(内含 @Qualifier),LoadBalancerAutoConfiguration 自动收集所有标注了 @LoadBalanced 的 RestTemplate,为每个 RestTemplate 添加 LoadBalancerInterceptor。拦截器拦截请求,提取 URL 中的服务名,调用 Ribbon 选择实例,替换 URL 中的服务名为 IP:Port,再发起真正的 HTTP 请求。
Q:Ribbon 和 Nginx 的负载均衡有什么区别?
Ribbon 是客户端负载均衡,调用方从注册中心拉取实例列表自己选;Nginx 是服务端负载均衡,所有流量经过中心节点转发。微服务内部调用用 Ribbon/LoadBalancer,对外暴露 API 用 Nginx。
Q:WeightedResponseTimeRule 的权重计算原理?
每 30 秒收集每个实例的平均响应时间,权重 = 总响应时间 - 该实例响应时间(响应越快权重越大),用累加权重区间做随机选择,权重越高被选中的概率越大。
Q:Ribbon 的重试机制是怎样的?
由 MaxAutoRetries(当前实例重试次数)和 MaxAutoRetriesNextServer(切换实例次数)控制。OkToRetryOnAllOperations=false 时只对 GET 请求重试。实际重试由 Feign 的 Retryer 或 Spring Retry 在 Feign/RestTemplate 层实现,而非 Ribbon 层。
Q:Ribbon 是如何与 Eureka 集成的?
DiscoveryEnabledNIWSServerList 实现 ServerList 接口,通过 EurekaClient 获取服务实例列表。NIWSDiscoveryPing 实现 IPing 接口,直接读取 Eureka 中实例的 InstanceStatus 判断是否存活。PollingServerListUpdater 每 30 秒定时从 Eureka 拉取最新实例列表更新到 ILoadBalancer 中。
