Skip to content

Ribbon 详解:客户端负载均衡

  Ribbon 是 Netflix 开源的,曾是 Spring Cloud 生态中服务调用的标配。它嵌入在 Feign 和 RestTemplate 中,让调用方自己从注册中心获取实例列表、选择实例、发起调用,省去了中心化负载均衡器的瓶颈。

一、为什么需要客户端负载均衡?

1.1 服务端 vs 客户端负载均衡

服务端负载均衡

客户端负载均衡

1.2 微服务中为什么用客户端负载均衡?

微服务内部调用走的是内网,不需要经过 Nginx。客户端负载均衡让调用方自己从注册中心获取实例列表并选择,好处是:

  1. 无中心瓶颈:不需要所有流量经过一个 LB 节点
  2. 更智能的路由:客户端知道所有实例的状态,可以做最小连接数、加权响应时间等决策
  3. 少一跳:调用方直连目标实例,减少一次网络转发

二、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 工作流程

下面深入到 Ribbon 内部,看这 5 个核心组件是如何协作完成以上三步的。

2.1 五大核心组件

Ribbon 由 5 个核心组件协作完成负载均衡:

                    ┌─────────────────────────────────┐
                    │         ILoadBalancer            │  ← 协调器,对外提供 chooseServer()
                    └──────────────┬──────────────────┘

        ┌──────────────────────────┼──────────────────────────┐
        │                          │                          │
        ▼                          ▼                          ▼
┌──────────────────┐   ┌────────────────────┐   ┌────────────────────┐
│    ServerList    │   │       IRule        │   │       IPing        │
│   获取实例列表     │   │    负载均衡策略      │   │     健康检查        │
└──────────────────┘   └────────────────────┘   └────────────────────┘
        │                                                  │
        └────────────────────┬─────────────────────────────┘

                  ┌──────────────────────┐
                  │   ServerListFilter   │  ← 过滤掉不健康的实例
                  └──────────────────────┘
组件接口职责
ServerListServerList<Server>从注册中心(Eureka/Nacos)获取服务实例列表
ServerListUpdaterServerListUpdater定时更新 ServerList,保证实例列表是最新的
IRuleIRule负载均衡策略,从实例列表中选一个
IPingIPing检查实例是否存活,剔除不健康实例
ServerListFilterServerListFilter<T>过滤实例列表(如按 Zone 过滤)
ILoadBalancerILoadBalancer协调上述组件,对外提供 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 检查新实例存活状态
      → 更新 upServerList

2.5 IPing — 健康检查

IPing 负责检查实例是否存活,不健康的实例从 upServerList 中移除。

实现类策略说明
DummyPing永远返回 true不检查,默认
NIWSDiscoveryPing比较 Eureka 状态检查 Eureka 中实例状态是否为 UP
PingUrlHTTP 请求 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 — 过滤实例

ServerListFilterupServerList 基础上进一步过滤,可以根据 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 = 210

3.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 — 可用性过滤

原理: 先过滤掉不可用的实例,再从剩余的实例中轮询选择。

过滤条件(同时满足任一即过滤):

  1. 熔断器打开(连续失败次数超过阈值,默认 3 次)
  2. 活跃连接数超过上限(默认 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: 2

4.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)。原因:

  1. 阻塞式 IO:Ribbon 基于 HttpURLConnectionApache HttpClient,都是阻塞式 IO,线程资源占用高
  2. Spring Cloud 拥抱响应式:Spring Cloud 2020+ 引入 Spring Cloud LoadBalancer,基于响应式(Reactive)架构,非阻塞,性能更好
  3. 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 中。