Ribbon 客户端负载均衡
导学
"现在订单服务通过 Eureka 已经知道库存服务有 3 个实例了,"小崔问白歌,"但每次调用时,我怎么决定发给哪个实例?总不会永远只打第一个吧?"
白歌在白板上画了一张图:"这就是客户端负载均衡要解决的问题。Netflix Ribbon 是目前最成熟的方案——虽然 Spring Cloud 2020.x 开始推荐 LoadBalancer,但 Ribbon 的设计思想是所有负载均衡组件的基石,理解它对后续学习至关重要。"
定位与问题场景
Eureka 返回了实例列表(如 192.168.1.10:8081, 192.168.1.11:8081, 192.168.1.12:8081),选哪个?怎么选?选了之后是否要重试?
这就是**客户端负载均衡(Client-Side Load Balancing)**的职责:
客户端负载均衡 vs 服务端负载均衡:
| 维度 | 客户端负载均衡(Ribbon) | 服务端负载均衡(Nginx) |
|---|---|---|
| 负载均衡位置 | 消费者进程内 | 独立进程/设备 |
| 实例感知 | 自动从注册中心获取 | 手动配置 upstream |
| 网络跳数 | 1 跳(消费者 → 提供者) | 2 跳(消费者 → LB → 提供者) |
| 灵活性 | 可按业务定制策略 | 通用策略 |
Ribbon 核心原理
架构与组件
核心流程:
- ServerList:定时从 Eureka 拉取实例列表
- ServerListFilter:按条件过滤(如仅保留同 Zone 的实例)
- IPing:心跳检测,剔除不可达实例
- IRule:根据策略从剩余列表中选择一个实例
IRule:七种内置负载均衡策略
| 策略 | 类名 | 工作原理 |
|---|---|---|
| 轮询 | RoundRobinRule | 按顺序循环选择 |
| 随机 | RandomRule | 随机选择 |
| 加权响应时间 | WeightedResponseTimeRule | 响应越快权重越高 |
| 可用性过滤 | AvailabilityFilteringRule | 过滤掉断路器跳闸、高并发的实例 |
| 最小并发 | BestAvailableRule | 选择并发请求数最小的实例 |
| 区域感知 | ZoneAvoidanceRule | 默认策略,优先同 Zone + 可用性过滤 |
| 重试 | RetryRule | 在指定时间内轮询重试 |
完整示例:飞翔科技订单服务负载均衡
场景描述
库存服务有 3 个实例,订单服务通过 Ribbon 实现负载均衡调用。
操作前后对比:
| 维度 | 引入 Ribbon 前 | 引入 Ribbon 后 |
|---|---|---|
| 实例选择 | 写死第一个 URL | 自动轮询 / 随机 / 加权 |
| 故障转移 | 单点故障即不可用 | 自动跳过失败实例 |
| 扩容感知 | 手动修改配置 | 自动感知新增实例 |
步骤一:依赖引入
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
步骤二:自定义负载均衡策略
@Configuration
public class RibbonConfig {
@Bean
public IRule ribbonRule() {
// 改为加权响应时间策略(响应越快的实例获得越多请求)
return new WeightedResponseTimeRule();
}
}
或者更精细地为指定服务配置策略:
@Configuration
// 注意:该配置类不能被 @ComponentScan 扫描到,否则会成为全局配置
public class InventoryRibbonConfig {
@Bean
public IRule ribbonRule() {
// 仅对库存服务使用随机策略
return new RandomRule();
}
}
@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient(
name = "FEIXIANG-INVENTORY-SERVICE",
configuration = InventoryRibbonConfig.class
)
public class OrderServiceApplication {
// ...
}
步骤三:订单服务调用(带重试)
@RestController
public class OrderController {
@Autowired
private RestTemplate restTemplate;
// 引入 Ribbon 前:硬编码 + 手动 try-catch
// String url = "http://192.168.1.100:8081/inventory/1001";
// try { restTemplate.getForObject(url, Map.class); } catch (Exception e) { ... }
// 引入 Ribbon 后:服务名 + 自动负载均衡
@GetMapping("/order/check-stock/{productId}")
public Map<String, Object> checkStock(@PathVariable Long productId) {
return restTemplate.getForObject(
"http://FEIXIANG-INVENTORY-SERVICE/inventory/" + productId,
Map.class
);
}
}
步骤四:重试配置
feixiang-inventory-service:
ribbon:
# 同一实例最大重试次数
MaxAutoRetries: 1
# 其他实例最大重试次数
MaxAutoRetriesNextServer: 2
# 是否所有操作都重试(GET 安全,POST 需谨慎)
OkToRetryOnAllOperations: false
# 连接超时
ConnectTimeout: 1000
# 读取超时
ReadTimeout: 3000
步骤五:验证负载均衡
启动 3 个库存服务实例(端口 8081 / 8082 / 8083),连续调用 9 次:
for i in {1..9}; do curl -s http://localhost:8080/order/check-stock/1001 | jq .servicePort; done
典型输出(轮询策略下):
8081, 8082, 8083, 8081, 8082, 8083, 8081, 8082, 8083
易错场景
1. Ribbon 超时时间 < Hystrix 超时时间
Ribbon 的 ConnectTimeout + ReadTimeout 如果大于 Hystrix 的超时时间,Hystrix 熔断器会先触发,导致 Ribbon 重试机制完全失效。正确配置:
Hystrix Timeout > (Ribbon ConnectTimeout + ReadTimeout) × (MaxAutoRetries + 1) × (MaxAutoRetriesNextServer + 1)
2. 饥饿加载导致首次调用超时
Ribbon 默认懒加载,首次调用时才初始化负载均衡器,可能触发超时:
ribbon:
eager-load:
enabled: true
clients: feixiang-inventory-service,feixiang-payment-service
3. POST 请求重试造成重复扣款
# ❌ 危险配置:所有操作(含 POST)都重试
feixiang-inventory-service.ribbon.OkToRetryOnAllOperations: true
# ✅ 安全配置:仅幂等的 GET 操作重试
feixiang-inventory-service.ribbon.OkToRetryOnAllOperations: false
4. Ribbon 配置类被全局扫描
@RibbonClient 引用的配置类不能被 @ComponentScan 或 @SpringBootApplication 扫描到,否则会成为所有 Ribbon Client 的全局配置。配置类应放在独立包中。
面试考点
Ribbon 的负载均衡发生在哪一层?
Ribbon 是客户端负载均衡,负载均衡逻辑运行在服务消费者的进程内。当
RestTemplate发起调用时,Ribbon 拦截请求,根据从注册中心获取的实例列表和配置的策略,选择一个实例发起真实 HTTP 请求。整个过程在消费者本地完成,不需要额外的网络跳转。
Ribbon 和 Nginx 的区别?
Ribbon 是客户端负载均衡(Consumer 进程内),Nginx 是服务端负载均衡(独立进程/设备)。Nginx 需要额外一跳网络开销,但可以集中管理;Ribbon 没有额外网络跳数,但策略逻辑分散在各消费者中。微服务架构中二者通常配合使用:Nginx 做外部流量入口的负载均衡,Ribbon 做内部服务间调用的负载均衡。
Ribbon 的默认策略是什么?为什么?
默认策略是
ZoneAvoidanceRule(区域感知),复合了可用性过滤。设计意图:优先选择同 Zone(同一机房/可用区)的实例,减少跨机房网络延迟;同时过滤掉断路器跳闸或并发过高的实例,保证调用成功率。
Ribbon 重试机制的注意事项?
- 仅对幂等操作(GET/HEAD/OPTIONS)重试,POST/PUT/DELETE 可能导致重复扣款
- 重试总次数 =
(MaxAutoRetries + 1) × (MaxAutoRetriesNextServer + 1) - 1- 重试超时时间必须小于上层熔断器的超时时间
小结
Ribbon 是微服务调用链的第二环:注册中心告诉你有哪些服务实例,Ribbon 决定调用哪个实例。内置的七种负载均衡策略覆盖了轮询、随机、加权等场景,配合重试机制可以实现基础的故障转移。但 Ribbon 已进入维护模式,Spring Cloud 2020.x 起官方推荐使用 Spring Cloud LoadBalancer——下一节我们将详细对比。