1. 概述
Spring Cloud 通过集成 Netflix Ribbon 提供了客户端负载均衡能力。而 Ribbon 的负载均衡机制还可以结合请求重试(retry)功能,进一步提升系统的容错性。
本文将深入探讨 Spring Cloud Netflix Ribbon 中的请求重试机制。
我们将从为什么需要重试讲起,然后搭建一个完整的示例应用,演示在服务短暂不可用时如何自动重试请求。整个过程会涵盖配置、依赖、实际行为观察以及重试间隔策略的定制。
2. 为什么需要重试
在微服务架构中,服务之间通过网络频繁交互。但云环境具有天然的不稳定性——网络抖动、服务短暂不可用、瞬时高负载等情况时有发生。
✅ 优雅处理失败、快速恢复,是构建高可用系统的核心原则之一。
⚠️ 很多故障是短暂的(transient)。比如一次 503 或 408 响应,稍等片刻再试可能就成功了。
通过自动重试,我们能显著提升系统的弹性(resilience),避免因短暂故障导致整个链路失败。
但也要注意:
❌ 过度重试可能加剧系统压力,甚至引发雪崩。
❌ 不合理的重试策略会增加整体延迟。
因此,重试不是“越多越好”,而是要有策略、有限制地进行。
3. 环境搭建
为了演示重试机制,我们需要两个 Spring Boot 服务:
weather-service
:提供天气信息的 REST 接口,模拟间歇性失败client-service
:调用 weather-service 的客户端,集成 Ribbon 并开启重试
3.1 天气服务(weather-service)
我们实现一个简单的 /weather
接口,通过配置控制其失败频率。当请求次数是某个数的倍数时,返回 503 SERVICE_UNAVAILABLE
。
@Value("${successful.call.divisor}")
private int divisor;
private int nrOfCalls = 0;
@GetMapping("/weather")
public ResponseEntity<String> weather() {
LOGGER.info("Providing today's weather information");
if (isServiceUnavailable()) {
return new ResponseEntity<>(HttpStatus.SERVICE_UNAVAILABLE);
}
LOGGER.info("Today's a sunny day");
return new ResponseEntity<>("Today's a sunny day", HttpStatus.OK);
}
private boolean isServiceUnavailable() {
return ++nrOfCalls % divisor != 0;
}
📌 说明:
divisor
控制失败频率,例如设为 5 表示每 5 次请求失败 4 次- 日志用于观察重试行为
3.2 客户端服务(client-service)
客户端使用 Spring Cloud Netflix Ribbon 实现负载均衡和重试。
3.2.1 配置 Ribbon 客户端
@Configuration
@RibbonClient(name = "weather-service", configuration = RibbonConfiguration.class)
public class WeatherClientRibbonConfiguration {
@LoadBalanced
@Bean
RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
@LoadBalanced
:标记 RestTemplate 启用 Ribbon 负载均衡@RibbonClient
:为weather-service
定制 Ribbon 配置
3.2.2 自定义 Ribbon 策略
public class RibbonConfiguration {
@Bean
public IPing ribbonPing() {
return new PingUrl();
}
@Bean
public IRule ribbonRule() {
return new RoundRobinRule();
}
}
IPing
:使用PingUrl
检查服务可用性IRule
:使用轮询(RoundRobin)策略分发请求
3.2.3 关闭 Eureka,手动指定服务列表
由于我们不使用服务发现,需在 application.yml
中手动配置服务实例列表:
weather-service:
ribbon:
eureka:
enabled: false
listOfServers: http://localhost:8021, http://localhost:8022
3.2.4 调用远程接口
@RestController
public class MyRestController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/client/weather")
public String weather() {
String result = this.restTemplate.getForObject("http://weather-service/weather", String.class);
return "Weather Service Response: " + result;
}
}
通过 http://weather-service/weather
调用,Ribbon 会自动选择可用实例。
4. 启用重试机制
4.1 配置 application.yml
在客户端的 application.yml
中添加以下 Ribbon 重试配置:
weather-service:
ribbon:
MaxAutoRetries: 3
MaxAutoRetriesNextServer: 1
retryableStatusCodes: 503, 408
OkToRetryOnAllOperations: true
关键参数说明:
参数 | 说明 |
---|---|
MaxAutoRetries |
在同一实例上重试次数(默认 0) |
MaxAutoRetriesNextServer |
切换到其他实例的重试次数(默认 0) |
retryableStatusCodes |
触发重试的 HTTP 状态码列表 |
OkToRetryOnAllOperations |
是否对所有 HTTP 方法重试(默认仅 GET) |
⚠️ 注意:非幂等操作(如 POST)开启重试需谨慎,避免重复提交。
4.2 添加 Spring Retry 依赖
Ribbon 的重试功能依赖 spring-retry
库,必须显式引入:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
否则重试不会生效(常见踩坑点)。
4.3 实际效果验证
启动服务
- 启动两个
weather-service
实例:- 实例1:端口 8021,
successful.call.divisor=5
- 实例2:端口 8022,
successful.call.divisor=2
- 实例1:端口 8021,
- 启动
client-service
:端口 8080
发起请求
访问 http://localhost:8080/client/weather
观察日志
weather service instance 1:
Providing today's weather information
Providing today's weather information
Providing today's weather information
Providing today's weather information
weather service instance 2:
Providing today's weather information
Today's a sunny day
可以看到:
- 实例1 连续被调用 4 次(均失败)
- 实例2 被调用 2 次,第 2 次成功
- 最终客户端收到成功响应
✅ 重试机制生效:先在同一实例重试,失败后切换到其他实例。
5. 重试退避策略(Backoff Policy)
5.1 默认行为
默认情况下,重试是立即进行的,没有延迟。底层使用的是 NoBackOffPolicy
。
但在高并发场景下,密集重试可能加剧服务压力,导致雪崩。
5.2 自定义退避策略
我们可以通过继承 RibbonLoadBalancedRetryFactory
来定制退避策略。
固定延迟(Fixed Backoff)
@Component
private class CustomRibbonLoadBalancedRetryFactory
extends RibbonLoadBalancedRetryFactory {
public CustomRibbonLoadBalancedRetryFactory(
SpringClientFactory clientFactory) {
super(clientFactory);
}
@Override
public BackOffPolicy createBackOffPolicy(String service) {
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(2000); // 2秒
return fixedBackOffPolicy;
}
}
指数退避(Exponential Backoff)
@Override
public BackOffPolicy createBackOffPolicy(String service) {
ExponentialBackOffPolicy exponentialBackOffPolicy =
new ExponentialBackOffPolicy();
exponentialBackOffPolicy.setInitialInterval(1000); // 初始1秒
exponentialBackOffPolicy.setMultiplier(2); // 倍增因子
exponentialBackOffPolicy.setMaxInterval(10000); // 最大10秒
return exponentialBackOffPolicy;
}
延迟序列:1s → 2s → 4s → 8s → 10s → 10s…
随机指数退避(ExponentialRandomBackOffPolicy)
在指数退避基础上加入随机因子,避免多个客户端同时重试。
// 示例:可能产生 1.5s, 3.4s, 6.2s, 9.8s, 10s...
5.3 策略选择建议
策略 | 适用场景 |
---|---|
Fixed | 流量小,简单控制 |
Exponential | 常规生产环境,逐步试探 |
ExponentialRandom | 高并发、多客户端场景,防重试风暴 |
✅ **推荐生产环境使用 ExponentialRandomBackOffPolicy
**,能有效分散重试压力。
6. 总结
本文系统介绍了如何在 Spring Cloud 应用中使用 Netflix Ribbon 实现失败请求的自动重试:
- ✅ 通过配置
MaxAutoRetries
和MaxAutoRetriesNextServer
控制重试次数 - ✅ 必须引入
spring-retry
依赖,否则重试不生效 - ✅ 可自定义
retryableStatusCodes
决定哪些错误码触发重试 - ✅ 通过
RibbonLoadBalancedRetryFactory
定制退避策略,避免重试风暴
重试是提升系统弹性的有效手段,但需结合超时、熔断等机制综合使用,才能构建真正健壮的微服务架构。
示例代码已上传至 GitHub:https://github.com/yourname/spring-cloud-ribbon-retry-demo