Spring重试机制详解:从入门到精通
📱 在分布式系统中,网络故障、服务超时是常见问题。Spring Retry通过优雅的重试机制,帮助我们构建更加稳健的应用系统。本文将深入探讨Spring重试机制的原理、使用方法和最佳实践。
一、概述
1.1 什么是重试机制
重试机制是指当某个操作失败时,系统会根据预定义的策略自动再次尝试执行该操作。在分布式系统中,这是必不可少的容错手段。
重试适用场景:
- 🌐 网络波动导致的临时故障(如超时、连接被重置)
- 🔄 远程服务的间歇性故障(毛刺故障)
- 💾 数据库连接池耗尽的临时情况
- ⚡ 竞态条件导致的写冲突
- 📦 消息队列不可用的短暂时间
- 🔐 分布式锁争夺导致的冲突
1.2 重试机制的核心要素
一个完整的重试机制通常包含以下要素:
| 要素 | 说明 | 示例 |
|---|---|---|
| 重试次数 | 最多尝试执行多少次 | 3次、5次 |
| 延迟策略 | 重试之间的等待时间 | 固定延迟、指数退避 |
| 异常判断 | 判断异常是否可重试 | IOException可重试,业务异常不可重试 |
| 超时控制 | 单次重试和总重试的超时 | 单次10秒,总计60秒 |
| 回调处理 | 重试成功/失败时的处理 | 记录日志、发送告警 |
| 幂等性保证 | 重试操作可以安全重复执行 | 使用业务ID去重 |
1.3 为什么需要Spring Retry
在微服务架构中,服务间调用会面临各种故障:
┌─────────────┐
│ 客户端 │
└──────┬──────┘
│ 网络故障、超时、限流
│ ↓
┌─────────────────────────────┐
│ Spring Retry 重试框架 │
│ ┌─────────────────────────┐ │
│ │ 1. 判断是否需要重试 │ │
│ │ 2. 计算延迟时间 │ │
│ │ 3. 执行重试 │ │
│ │ 4. 监控重试过程 │ │
│ └─────────────────────────┘ │
└─────────────────────────────┘
│ 恢复 或 最终失败
↓
业务逻辑继续
Spring Retry通过AOP代理和模板方法模式,为我们提供了:
- ✅ 声明式编程(注解方式)
- ✅ 编程式方式(模板类)
- ✅ 灵活的重试策略
- ✅ 丰富的延迟算法
- ✅ 完整的监听器支持
二、Spring Retry框架核心概念
2.1 Spring Retry简介
Spring Retry是Spring框架中提供的重试支持库。它通过AOP代理和模板方法设计模式,为应用程序提供了强大的重试功能。
依赖配置:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>2.0.0</version>
</dependency>
<!-- AOP支持(必需) -->
<dependency>
<groupId>org.springframework.aspects</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.0</version>
</dependency>
2.2 核心接口和类
RetryTemplate
Spring Retry的核心类,用于执行重试逻辑。它是线程安全的,可以在多线程环境中使用。
// 声明式使用示例
public class RetryTemplateExample {
private RetryTemplate retryTemplate;
public String execute() {
return retryTemplate.execute(
context -> callExternalService(), // RetryCallback
context -> fallbackLogic() // RecoveryCallback
);
}
}
特点:
- 线程安全
- 可配置的重试策略
- 支持监听器
- 提供恢复机制
RetryPolicy
定义重试策略,包括:
- 最多重试次数
- 哪些异常可以重试
- 什么时候停止重试
// 常见实现
SimpleRetryPolicy // 最多重试N次
TimeoutRetryPolicy // 在指定时间内重试
ExceptionClassifierRetryPolicy // 根据异常类型判断
CompositeRetryPolicy // 组合多个策略
BackOffPolicy
定义重试之间的延迟策略,是防止雪崩的关键:
// 常见实现
NoBackOffPolicy // 立即重试
FixedBackOffPolicy // 固定延迟(如1秒)
LinearBackOffPolicy // 线性增加延迟
ExponentialBackOffPolicy // 指数增长延迟(推荐)
ExponentialRandomBackOffPolicy // 指数增长+随机(防止雷鸣羊群)
RecoveryCallback
所有重试都失败后的恢复处理逻辑。这是降级方案的入口点:
// 恢复回调示例
(context) -> {
log.error("重试全部失败,尝试降级处理");
return defaultValue; // 返回默认值
}
三、@Retryable注解方式
3.1 基本使用
@Retryable注解是最简洁的声明式重试方式。
第一步:启用重试机制
@SpringBootApplication
@EnableRetry // ⚠️ 必须添加!
public class RetryDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RetryDemoApplication.class, args);
}
}
第二步:在方法上添加@Retryable注解
@Service
public class UserService {
@Retryable(
maxAttempts = 3, // 最多重试3次
backoff = @Backoff(delay = 1000) // 延迟1秒
)
public User getUser(String userId) {
// 调用外部API,可能失败
return remoteUserApi.fetchUser(userId);
}
// ⚠️ 注意:恢复方法的参数必须匹配,异常类型要对应
@Recover
public User recoverGetUser(Exception e, String userId) {
log.error("重试全部失败,userId: {}", userId, e);
// 返回默认用户或从本地缓存获取
return new User(userId, "Unknown User", "default@email.com");
}
}
3.2 重要参数详解
| 参数 | 默认值 | 说明 | 示例 |
|---|---|---|---|
| value | {} | 指定重试的异常类型(数组) | {IOException.class, TimeoutException.class} |
| maxAttempts | 3 | 最大重试次数(包括第一次) | 5表示1次+4次重试 |
| delay | 0ms | 重试间隔时间(毫秒) | 1000 = 1秒 |
| multiplier | 0 | 延迟时间的乘数(用于指数退避) | 1.5表示每次乘以1.5 |
| maxDelay | 0 | 最大延迟时间(毫秒) | 30000 = 30秒 |
| stateful | false | 是否有状态(同一异常实例才重试) | true用于复杂场景 |
| listeners | {} | 重试监听器 | 自定义监听器 |
3.3 异常处理详解
@Service
public class PaymentService {
// ✅ 只重试IOException和TimeoutException
// ❌ 不重试其他异常(如业务异常)
@Retryable(
value = {
IOException.class,
SocketTimeoutException.class
},
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 1.5)
)
public PaymentResult processPayment(Order order) {
log.info("尝试处理支付,订单ID: {}", order.getId());
// 可能抛出IOException - 会重试
// 可能抛出SocketTimeoutException - 会重试
// 可能抛出BusinessException - 不会重试,直接抛出
return paymentGateway.charge(order);
}
@Recover
public PaymentResult recoverPayment(
IOException e, Order order) {
log.error("支付失败,订单号: {}, 异常: {}",
order.getId(), e.getMessage());
// 记录待处理交易,人工处理
pendingPaymentService.save(
new PendingPayment(order.getId()));
throw new PaymentException(
"支付处理中,请稍候再试");
}
}
3.4 实战示例:远程API调用
@Service
public class WeatherService {
@Autowired
private RestTemplate restTemplate;
// 场景:调用第三方天气API
@Retryable(
value = {
ConnectException.class,
SocketTimeoutException.class,
HttpClientErrorException.class
},
maxAttempts = 3,
backoff = @Backoff(
delay = 2000, // 初始延迟2秒
multiplier = 1.5, // 每次增加1.5倍
maxDelay = 10000 // 最多延迟10秒
),
listeners = {"retryListener"}
)
public WeatherData getWeather(String city) {
log.info("第{}次调用天气API,城市: {}",
getCurrentRetryCount(), city);
String url = String.format(
"https://api.weather.com/city/%s", city);
return restTemplate.getForObject(
url, WeatherData.class);
}
@Recover
public WeatherData recoverGetWeather(
Exception e, String city) {
log.warn("天气API调用失败,使用本地缓存数据");
return weatherCache.getLastWeather(city);
}
private int getCurrentRetryCount() {
RetryContext context =
RetryContextHolder.getContext();
return context != null ?
context.getRetryCount() : 0;
}
}
四、RetryTemplate编程方式
4.1 基本使用
RetryTemplate提供了更灵活的编程式重试方式,相比@Retryable,它提供了对重试过程的更细粒度控制。
配置重试模板:
@Configuration
public class RetryConfiguration {
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate =
new RetryTemplate();
// 设置重试策略:最多重试3次
SimpleRetryPolicy retryPolicy =
new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(3);
retryTemplate.setRetryPolicy(retryPolicy);
// 设置延迟策略:固定延迟2秒
FixedBackOffPolicy backOffPolicy =
new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(2000);
retryTemplate.setBackOffPolicy(backOffPolicy);
return retryTemplate;
}
}
在业务代码中使用:
@Service
public class OrderService {
@Autowired
private RetryTemplate retryTemplate;
@Autowired
private OrderRepository orderRepository;
public Order createOrder(Order order)
throws Exception {
// 使用RetryTemplate执行业务逻辑
return retryTemplate.execute(
// RetryCallback:重试的业务逻辑
context -> {
log.info("第{}次尝试创建订单",
context.getRetryCount() + 1);
return orderRepository.save(order);
},
// RecoveryCallback:恢复逻辑
context -> {
log.error("订单创建失败,重试{}次",
context.getRetryCount());
// 记录失败的订单
failedOrderLog.save(order);
// 返回默认值或抛出异常
return null;
}
);
}
}
4.2 重试策略详解
SimpleRetryPolicy - 简单次数限制
@Bean
public RetryTemplate simpleRetryTemplate() {
RetryTemplate template = new RetryTemplate();
SimpleRetryPolicy policy =
new SimpleRetryPolicy();
policy.setMaxAttempts(3); // 最多3次
template.setRetryPolicy(policy);
return template;
}
TimeoutRetryPolicy - 时间限制
@Bean
public RetryTemplate timeoutRetryTemplate() {
RetryTemplate template = new RetryTemplate();
TimeoutRetryPolicy policy =
new TimeoutRetryPolicy();
policy.setTimeout(60000); // 60秒内重试
template.setRetryPolicy(policy);
return template;
}
ExceptionClassifierRetryPolicy - 异常分类
@Bean
public RetryTemplate classifierRetryTemplate() {
RetryTemplate template = new RetryTemplate();
// 定义哪些异常可重试
Map<Class<? extends Throwable>, Boolean>
retryableMap = new HashMap<>();
retryableMap.put(IOException.class, true); // 重试
retryableMap.put(SocketTimeoutException.class,
true); // 重试
retryableMap.put(BusinessException.class,
false); // 不重试
SimpleClassifierRetryPolicy policy =
new SimpleClassifierRetryPolicy(
retryableMap);
template.setRetryPolicy(policy);
return template;
}
CompositeRetryPolicy - 组合策略
@Bean
public RetryTemplate compositeRetryTemplate() {
RetryTemplate template = new RetryTemplate();
// 组合多个策略:次数限制 + 时间限制
CompositeRetryPolicy policy =
new CompositeRetryPolicy();
SimpleRetryPolicy maxAttemptsPolicy =
new SimpleRetryPolicy();
maxAttemptsPolicy.setMaxAttempts(5);
TimeoutRetryPolicy timeoutPolicy =
new TimeoutRetryPolicy();
timeoutPolicy.setTimeout(120000); // 2分钟
policy.setPolicies(new RetryPolicy[]{
maxAttemptsPolicy,
timeoutPolicy
});
template.setRetryPolicy(policy);
return template;
}
4.3 高级配置示例
@Configuration
public class AdvancedRetryConfig {
@Bean("fastRetryTemplate")
public RetryTemplate fastRetryTemplate() {
RetryTemplate template =
new RetryTemplate();
// 快速重试(网络故障)
SimpleRetryPolicy policy =
new SimpleRetryPolicy();
policy.setMaxAttempts(3);
template.setRetryPolicy(policy);
// 无延迟
template.setBackOffPolicy(
new NoBackOffPolicy());
return template;
}
@Bean("standardRetryTemplate")
public RetryTemplate standardRetryTemplate() {
RetryTemplate template =
new RetryTemplate();
// 标准重试(RPC调用)
SimpleRetryPolicy policy =
new SimpleRetryPolicy();
policy.setMaxAttempts(3);
template.setRetryPolicy(policy);
// 指数退避
ExponentialBackOffPolicy backoff =
new ExponentialBackOffPolicy();
backoff.setInitialInterval(1000); // 1秒
backoff.setMultiplier(1.5); // 乘以1.5
backoff.setMaxInterval(10000); // 最多10秒
template.setBackOffPolicy(backoff);
return template;
}
@Bean("slowRetryTemplate")
public RetryTemplate slowRetryTemplate() {
RetryTemplate template =
new RetryTemplate();
// 缓慢重试(数据库操作)
SimpleRetryPolicy policy =
new SimpleRetryPolicy();
policy.setMaxAttempts(5);
template.setRetryPolicy(policy);
// 缓慢的指数退避
ExponentialBackOffPolicy backoff =
new ExponentialBackOffPolicy();
backoff.setInitialInterval(2000); // 2秒
backoff.setMultiplier(2.0); // 乘以2
backoff.setMaxInterval(60000); // 最多1分钟
template.setBackOffPolicy(backoff);
return template;
}
}
五、延迟策略详解
5.1 常见延迟策略对比
选择合适的延迟策略对系统性能至关重要。
| 策略 | 延迟行为 | 算法 | 适用场景 | 风险 |
|---|---|---|---|---|
| NoBackOff | 立即重试,无延迟 | - | 瞬时故障、毛刺 | 可能加重已有负担 |
| FixedBackOff | 固定延迟(1秒、2秒等) | delay = D | 普通网络故障 | 可能不够灵活 |
| LinearBackOff | 线性增加延迟 | delay = D × i | 中等故障恢复 | 增长缓慢 |
| Exponential | 指数增长延迟(推荐) | delay = D × multiplier^n | 防止雪崩 | 延迟可能过长 |
| ExponentialRandom | 指数增长+随机 | delay = random × exponential | 防止雷鸣羊群 | 最复杂 |
5.2 指数退避详解(最佳实践)
指数退避是处理分布式故障的最佳实践。它通过逐步增加延迟时间,给被调用方充足的恢复时间。
问题场景:
客户端不断重试
↓
服务端压力增加
↓
服务端处理更慢
↓
客户端超时更多,重试更频繁
↓
形成雪崩!❌
指数退避解决方案:
第1次:立即失败
第2次:等待1秒后重试
第3次:等待1.5秒后重试 (1 × 1.5)
第4次:等待2.25秒后重试 (1.5 × 1.5)
第5次:等待3.375秒后重试 (2.25 × 1.5)
...
总延迟时间逐步增加,给服务恢复时间 ✅
代码实现:
// 方式1:注解方式
@Retryable(
value = {RemoteServiceException.class},
maxAttempts = 5,
backoff = @Backoff(
delay = 1000, // 初始延迟:1秒
multiplier = 2.0, // 每次乘以2倍
maxDelay = 30000 // 最多延迟30秒
)
)
public String callRemoteService() {
// 重试延迟序列: 1s -> 2s -> 4s -> 8s -> 16s
// (后面会被maxDelay限制在30s以内)
return remoteApi.fetch();
}
// 方式2:编程方式
@Bean
public RetryTemplate exponentialRetryTemplate() {
RetryTemplate template =
new RetryTemplate();
SimpleRetryPolicy policy =
new SimpleRetryPolicy();
policy.setMaxAttempts(5);
template.setRetryPolicy(policy);
ExponentialBackOffPolicy backoff =
new ExponentialBackOffPolicy();
backoff.setInitialInterval(1000); // 1秒
backoff.setMultiplier(2.0); // 2倍增长
backoff.setMaxInterval(30000); // 最多30秒
template.setBackOffPolicy(backoff);
return template;
}
参数选择建议:
场景:调用支付API(关键业务)
- delay: 2000(2秒,留出足够时间)
- multiplier: 1.5(平衡的增长速度)
- maxDelay: 30000(最多等30秒)
- maxAttempts: 5(共5次)
场景:调用缓存服务(非关键)
- delay: 100(100毫秒,快速重试)
- multiplier: 2.0(快速增长)
- maxDelay: 5000(最多5秒)
- maxAttempts: 3(共3次)
场景:调用数据库(最关键)
- delay: 500(500毫秒)
- multiplier: 1.5(温和增长)
- maxDelay: 60000(最多60秒)
- maxAttempts: 3(共3次)
5.3 防止雷鸣羊群现象
当大量客户端同时重试时,可能在同一时间点都发起请求,形成"雷鸣羊群"现象。
// 使用随机化的指数退避
@Bean
public RetryTemplate randomExponentialTemplate() {
RetryTemplate template =
new RetryTemplate();
SimpleRetryPolicy policy =
new SimpleRetryPolicy();
policy.setMaxAttempts(4);
template.setRetryPolicy(policy);
// 指数退避基础上加随机化
ExponentialRandomBackOffPolicy backoff =
new ExponentialRandomBackOffPolicy();
backoff.setInitialInterval(1000);
backoff.setMultiplier(2.0);
backoff.setMaxInterval(30000);
template.setBackOffPolicy(backoff);
return template;
}
// 延迟时间 = Random(0, exponential)
// 例如:随机(0, 1s), 随机(0, 2s), 随机(0, 4s)...
// 这样可以分散客户端的重试时间点
六、高级特性与最佳实践
6.1 自定义重试策略
有时需要实现复杂的业务逻辑重试。
// 自定义重试策略:支持特殊的业务逻辑
public class BusinessRetryPolicy
implements RetryPolicy {
private static final int MAX_ATTEMPTS = 3;
private static final long TIMEOUT = 60000; // 60秒
@Override
public boolean canRetry(RetryContext context) {
Throwable lastException =
context.getLastThrowable();
// 1. 检查重试次数
if (context.getRetryCount() >= MAX_ATTEMPTS) {
return false;
}
// 2. 检查超时时间
long elapsed = System.currentTimeMillis() -
context.getStartTime();
if (elapsed > TIMEOUT) {
return false;
}
// 3. 检查异常类型
if (lastException instanceof IOException) {
return true; // 网络异常可重试
}
if (lastException instanceof
SocketTimeoutException) {
return true; // 超时异常可重试
}
if (lastException instanceof
BusinessException) {
// 业务异常根据具体情况判断
BusinessException be =
(BusinessException) lastException;
return be.isRetryable();
}
return false; // 其他异常不重试
}
@Override
public RetryContext open(
RetryContext parentContext) {
return new RetryContextSupport(
parentContext);
}
@Override
public void close(RetryContext context) {
// 清理资源
}
@Override
public void registerThrowable(
RetryContext context,
Throwable throwable) {
context.registerThrowable(throwable);
}
}
// 使用自定义策略
@Bean
public RetryTemplate customRetryTemplate() {
RetryTemplate template =
new RetryTemplate();
template.setRetryPolicy(
new BusinessRetryPolicy());
return template;
}
6.2 重试监听器
通过RetryListener监听重试过程中的关键事件,用于监控、日志、告警等。
@Component
public class RetryMonitorListener
implements RetryListener {
@Override
public <T, E extends Throwable> void open(
RetryContext context,
RetryCallback<T, E> callback) {
log.info("开始重试: {}",
callback.getClass().getSimpleName());
context.setAttribute("startTime",
System.currentTimeMillis());
}
@Override
public <T, E extends Throwable> void onSuccess(
RetryContext context,
RetryCallback<T, E> callback,
T result) {
long duration =
System.currentTimeMillis() -
(Long)context.getAttribute("startTime");
log.info(
"重试成功! 耗时: {}ms, 重试次数: {}",
duration, context.getRetryCount());
// 上报成功指标
metrics.recordRetrySuccess(
duration, context.getRetryCount());
}
@Override
public <T, E extends Throwable> void onError(
RetryContext context,
RetryCallback<T, E> callback,
Throwable throwable) {
log.warn(
"第{}次重试失败: {}",
context.getRetryCount(),
throwable.getMessage());
// 上报失败指标
metrics.recordRetryFailure(
throwable.getClass().getSimpleName());
}
@Override
public void close(RetryContext context) {
long duration =
System.currentTimeMillis() -
(Long)context.getAttribute("startTime");
log.info(
"重试过程结束,总耗时: {}ms",
duration);
}
}
// 注册监听器
@Bean
public RetryTemplate
retryTemplateWithListener(
RetryMonitorListener listener) {
RetryTemplate template =
new RetryTemplate();
template.registerListener(listener);
// ... 其他配置
return template;
}
6.3 与熔断器结合
在生产环境中,通常需要将重试与熔断器模式结合使用。
@Service
public class ResilientService {
@Autowired
private RetryTemplate retryTemplate;
@Autowired
private CircuitBreaker circuitBreaker;
/**
* 执行带有容错能力的业务逻辑
*
* 工作流程:
* 1. 熔断器检查(快速失败)
* 2. 进入重试逻辑
* 3. 重试次数内恢复 → 成功
* 4. 超出重试次数 → 执行降级逻辑
*/
public String executeWithResilience(
String userId) {
// 外层:熔断器保护
return circuitBreaker.execute(() ->
// 中层:重试机制
retryTemplate.execute(
context -> {
log.info("尝试调用服务,用户: {}",
userId);
return callRemoteService(userId);
},
context -> {
// 降级逻辑
log.warn("重试失败,执行降级方案");
return getDefaultResponse(userId);
}
)
);
}
private String callRemoteService(
String userId) {
// 可能失败的业务逻辑
return remoteServiceClient.getUser(userId)
.getName();
}
private String getDefaultResponse(
String userId) {
// 降级方案:从缓存或数据库获取
return userCache.get(userId)
.orElse("Default User");
}
}
6.4 常见最佳实践
1. 合理选择重试异常
// ✅ 应该重试的异常
IOException.class // 网络异常
SocketTimeoutException.class // 连接超时
ConnectException.class // 连接失败
HttpServerErrorException.class // 5xx错误
TimeoutException.class // 超时
// ❌ 不应该重试的异常
BusinessException.class // 业务异常
ValidationException.class // 验证异常
NotFoundException.class // 资源不存在
AccessDeniedException.class // 权限异常
IllegalArgumentException.class // 参数错误
2. 确保幂等性
重试的操作必须是幂等的,避免重复提交。
/**
* ✅ 幂等的操作:多次执行结果相同
*/
@Retryable(maxAttempts = 3)
public void updateUserStatus(
String userId, String status) {
// 使用UPDATE语句 + WHERE条件
// 多次执行结果相同
userRepository.updateStatus(
userId, status);
}
/**
* ❌ 非幂等的操作:多次执行结果不同
*/
@Retryable(maxAttempts = 3) // ⚠️ 危险!
public void transferMoney(
String fromId, String toId,
BigDecimal amount) {
// 多次执行会多次转账!
accountService.deduct(fromId, amount);
accountService.add(toId, amount);
}
/**
* ✅ 改为幂等操作
*/
@Retryable(maxAttempts = 3)
public void transferMoney(
String transferId, // 业务ID
String fromId, String toId,
BigDecimal amount) {
// 检查转账是否已处理
if (transferHistoryService.exists(
transferId)) {
return; // 已处理,直接返回
}
// 执行转账
accountService.deduct(fromId, amount);
accountService.add(toId, amount);
// 记录转账历史
transferHistoryService.save(
new TransferHistory(
transferId, fromId, toId,
amount));
}
3. 设置合理的超时和重试次数
// 根据业务重要性设置不同的策略
// 关键业务:支付
@Retryable(
maxAttempts = 5,
backoff = @Backoff(
delay = 2000,
multiplier = 1.5,
maxDelay = 30000
)
)
public void paymentProcess(Order order) { }
// 普通业务:用户信息更新
@Retryable(
maxAttempts = 3,
backoff = @Backoff(
delay = 500,
multiplier = 1.5,
maxDelay = 10000
)
)
public void updateUserInfo(User user) { }
// 非关键业务:日志同步
@Retryable(
maxAttempts = 2,
backoff = @Backoff(delay = 100)
)
public void syncAuditLog(Log log) { }
4. 记录重试指标
@Component
public class RetryMetricsListener
implements RetryListener {
@Autowired
private MeterRegistry meterRegistry;
@Override
public <T, E extends Throwable> void onError(
RetryContext context,
RetryCallback<T, E> callback,
Throwable throwable) {
// 记录重试指标
String methodName =
callback.getClass().getSimpleName();
String exceptionName =
throwable.getClass().getSimpleName();
// 记录计数器
meterRegistry.counter(
"retry.failure",
"method", methodName,
"exception", exceptionName
).increment();
// 记录重试次数
meterRegistry.gauge(
"retry.attempts",
() -> context.getRetryCount(),
"method", methodName
);
}
}
七、常见问题与排查
7.1 重试不生效的问题
症状: 添加了@Retryable但不生效
原因排查清单:
// ❌ 问题1:启动类没加@EnableRetry
@SpringBootApplication
// 缺少这一行!
public class Application { }
// ✅ 修复
@SpringBootApplication
@EnableRetry
public class Application { }
// ❌ 问题2:方法是private(AOP无法代理)
@Service
public class UserService {
@Retryable(maxAttempts = 3)
private User getUser() { } // ❌ 不生效
}
// ✅ 修复:改为public
@Service
public class UserService {
@Retryable(maxAttempts = 3)
public User getUser() { } // ✅ 生效
}
// ❌ 问题3:异常类型不匹配
@Service
public class UserService {
@Retryable(value = IOException.class)
public User getUser() {
throw new RuntimeException(); // 不是IOException
}
}
// ✅ 修复:异常类型要对应
@Service
public class UserService {
@Retryable(
value = {IOException.class,
RuntimeException.class}
)
public User getUser() {
throw new RuntimeException(); // 会重试
}
}
// ❌ 问题4:没有引入spring-aspects依赖
// pom.xml中缺少spring-aspects
// ✅ 修复:添加依赖
<dependency>
<groupId>org.springframework.aspects</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.0</version>
</dependency>
// ❌ 问题5:@Recover方法签名错误
@Service
public class UserService {
@Retryable(maxAttempts = 3)
public User getUser(String userId) { }
@Recover
public User recover(Exception e) { } // ❌ 少了userId参数
}
// ✅ 修复:@Recover方法参数要和@Retryable一致
@Service
public class UserService {
@Retryable(maxAttempts = 3)
public User getUser(String userId) { }
@Recover
public User recover(Exception e,
String userId) { } // ✅ 包含userId
}
调试技巧:
// 1. 查看是否生成了代理
ApplicationContext context =
SpringApplication.run(Application.class);
UserService userService =
context.getBean(UserService.class);
log.info("Bean类型: {}",
userService.getClass().getName());
// 输出:com.example.UserService$$CGLIB$...
// 说明已生成代理,@Retryable应该生效
// 2. 打印重试日志
@Retryable(maxAttempts = 3)
public void myMethod() {
log.info("执行业务逻辑");
throw new IOException();
}
// 3. 使用RetryListener监听
@Bean
public RetryListener debugListener() {
return new RetryListener() {
@Override
public <T, E extends Throwable>
void onError(RetryContext context,
RetryCallback<T, E> cb,
Throwable e) {
log.error("重试失败: 第{}次",
context.getRetryCount());
}
};
}
7.2 内存泄漏问题
症状: 重试过程中堆内存不断增长
原因: 大量的Throwable对象积累在内存中
// ❌ 可能导致内存泄漏的代码
@Service
public class BadRetryService {
@Retryable(maxAttempts = 100) // 过多重试
public void riskyOperation() {
// 每次失败都会创建Throwable对象
throw new RuntimeException();
}
}
// ✅ 修复
@Service
public class GoodRetryService {
@Retryable(
maxAttempts = 3, // 限制次数
backoff = @Backoff(delay = 1000)
)
public void safeOperation() {
throw new RuntimeException();
}
}
// 自定义策略清理异常
public class MemorySafeRetryPolicy
implements RetryPolicy {
private int maxAttempts = 3;
@Override
public boolean canRetry(
RetryContext context) {
if (context.getRetryCount() >=
maxAttempts) {
// 清理异常引用,防止内存泄漏
context.setLastThrowable(null);
return false;
}
return true;
}
// 其他方法...
}
7.3 性能问题
症状: 重试导致响应时间过长
优化方案:
// ❌ 性能问题的配置
@Retryable(
maxAttempts = 10, // 太多
backoff = @Backoff(
delay = 5000, // 延迟太长
multiplier = 3.0 // 增长太快
)
)
public void slowRetry() { }
// ✅ 性能优化的配置
@Retryable(
maxAttempts = 3, // 3次足够
backoff = @Backoff(
delay = 100, // 快速重试
multiplier = 1.5,
maxDelay = 5000
)
)
public void fastRetry() { }
// 方案1:区分关键和非关键操作
@Service
public class HybridRetryService {
// 关键操作:更多重试
@Retryable(
maxAttempts = 5,
backoff = @Backoff(delay = 2000)
)
public void criticalOperation() { }
// 非关键操作:快速失败
@Retryable(
maxAttempts = 2,
backoff = @Backoff(delay = 100)
)
public void normalOperation() { }
}
// 方案2:异步重试
@Service
public class AsyncRetryService {
@Async
@Retryable(maxAttempts = 3)
public CompletableFuture<String>
asyncOperation() {
// 不阻塞主线程
return CompletableFuture
.completedFuture("result");
}
}
// 方案3:使用超时控制
@Bean
public RetryTemplate
timeoutLimitedRetryTemplate() {
RetryTemplate template =
new RetryTemplate();
// 单次操作不超过30秒
template.setRetryPolicy(
new TimeoutRetryPolicy() {{
setTimeout(30000);
}}
);
return template;
}
八、综合实战案例
8.1 微服务订单系统
完整的电商订单处理流程,涉及多个微服务调用:
@Service
@Transactional
public class OrderServiceImpl
implements OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private InventoryService inventoryService;
@Autowired
private PaymentService paymentService;
@Autowired
private ShippingService shippingService;
@Autowired
private RetryTemplate retryTemplate;
/**
* 创建订单 - 完整流程
*
* 流程图:
* 预留库存 → 创建订单 → 支付 → 创建物流 → 成功
* ↓(失败) ↓(失败) ↓ ↓
* 重试3x 回滚库存 重试5x 重试3x
*/
@Override
public OrderResponse createOrder(
CreateOrderRequest request) {
InventoryReservation reservation = null;
try {
// 1. 预留库存(可重试)
reservation = reserveInventory(
request.getItems());
log.info("库存预留成功,预留ID: {}",
reservation.getId());
// 2. 创建订单记录
Order order = new Order();
order.setUserId(request.getUserId());
order.setItems(request.getItems());
order.setReservationId(
reservation.getId());
order.setStatus(OrderStatus.CREATED);
order = orderRepository.save(order);
log.info("订单已创建,订单ID: {}",
order.getId());
// 3. 处理支付(可重试)
PaymentResult paymentResult =
processPayment(order);
order.setPaymentId(
paymentResult.getPaymentId());
order.setStatus(OrderStatus.PAID);
order = orderRepository.save(order);
log.info("支付成功,支付ID: {}",
paymentResult.getPaymentId());
// 4. 创建物流单(可重试)
ShippingOrder shipping =
createShipping(order);
order.setShippingId(shipping.getId());
order.setStatus(OrderStatus.SHIPPED);
order = orderRepository.save(order);
log.info("物流单已创建,物流ID: {}",
shipping.getId());
return new OrderResponse(
order.getId(),
OrderStatus.SUCCESS.toString(),
"订单创建成功");
} catch (Exception e) {
log.error("订单创建失败", e);
// 回滚库存预留
if (reservation != null) {
try {
inventoryService
.releaseReservation(
reservation.getId());
} catch (Exception ex) {
log.error("释放库存失败", ex);
}
}
throw new OrderException(
"订单创建失败:" + e.getMessage(),
e);
}
}
/**
* 预留库存 - 可重试
*/
@Retryable(
value = {
InventoryException.class,
ConnectException.class,
SocketTimeoutException.class
},
maxAttempts = 3,
backoff = @Backoff(
delay = 500,
multiplier = 1.5
)
)
private InventoryReservation
reserveInventory(
List<OrderItem> items) {
log.info("正在预留库存,物品数: {}",
items.size());
return inventoryService
.reserveItems(items);
}
/**
* 预留库存失败的恢复逻辑
*/
@Recover
private InventoryReservation
recoverReserveInventory(
InventoryException e,
List<OrderItem> items) {
log.error("库存预留失败,无法重试", e);
throw new OrderException(
"库存不足或系统繁忙,请稍后重试");
}
/**
* 处理支付 - 可重试(最关键)
*/
@Retryable(
value = {
PaymentException.class,
SocketTimeoutException.class,
ConnectException.class
},
maxAttempts = 5,
backoff = @Backoff(
delay = 1000,
multiplier = 2.0,
maxDelay = 30000
)
)
private PaymentResult
processPayment(Order order) {
log.info("正在处理支付,订单ID: {}, " +
"金额: {}",
order.getId(),
order.getTotal());
return paymentService.charge(
order);
}
/**
* 支付失败的恢复逻辑
*/
@Recover
private PaymentResult
recoverProcessPayment(
PaymentException e,
Order order) {
log.error("支付处理失败,订单ID: {}",
order.getId(), e);
// 记录待处理交易
PendingPayment pending =
new PendingPayment();
pending.setOrderId(order.getId());
pending.setAmount(order.getTotal());
pending.setReason(e.getMessage());
pending.setStatus(
PendingPaymentStatus.PENDING);
pendingPaymentService.save(pending);
// 抛出异常,让前端知道支付正在处理
throw new PaymentException(
"支付处理中,请勿关闭页面");
}
/**
* 创建物流单 - 可重试
*/
@Retryable(
value = {
ShippingException.class,
ConnectException.class
},
maxAttempts = 3,
backoff = @Backoff(delay = 2000)
)
private ShippingOrder
createShipping(Order order) {
log.info("正在创建物流单,订单ID: {}",
order.getId());
return shippingService
.createShipping(order);
}
/**
* 创建物流单失败的恢复逻辑
*/
@Recover
private ShippingOrder
recoverCreateShipping(
ShippingException e,
Order order) {
log.error("物流单创建失败,订单ID: {}",
order.getId(), e);
// 降级处理:记录未决物流单,稍后处理
DeferredShipping deferred =
new DeferredShipping();
deferred.setOrderId(order.getId());
deferred.setReason(e.getMessage());
deferredShippingService.save(deferred);
// 返回空或临时物流单
return null;
}
}
// 辅助类
@Data
@Entity
public class Order {
@Id
private String id;
private String userId;
private List<OrderItem> items;
private String reservationId;
private String paymentId;
private String shippingId;
private OrderStatus status;
private BigDecimal total;
private LocalDateTime createTime;
}
@Data
public class OrderResponse {
private String orderId;
private String status;
private String message;
}
public enum OrderStatus {
CREATED,
PAID,
SHIPPED,
SUCCESS,
FAILED
}
8.2 第三方API集成示例
@Service
public class WeatherApiService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private WeatherCache weatherCache;
/**
* 获取天气信息
*
* 重试策略:
* - 最多重试3次
* - 延迟:1秒 → 1.5秒 → 2.25秒
* - 最多延迟10秒
*/
@Retryable(
value = {
ConnectException.class,
SocketTimeoutException.class,
HttpClientErrorException.class,
HttpServerErrorException.class
},
maxAttempts = 3,
backoff = @Backoff(
delay = 1000,
multiplier = 1.5,
maxDelay = 10000
)
)
public WeatherData getWeather(
String city) {
log.info("查询天气信息,城市: {}", city);
String url = String.format(
"https://api.weather.com/v1/" +
"weather?city=%s&key=%s",
city,
weatherApiKey);
try {
WeatherData data =
restTemplate.getForObject(
url, WeatherData.class);
// 缓存结果
weatherCache.put(city, data);
return data;
} catch (HttpClientErrorException e) {
// 4xx错误(如无效的城市)
// 不应该重试
if (e.getStatusCode()
.is4xxClientError()) {
throw e;
}
// 5xx错误可以重试
throw new HttpServerErrorException(
e.getStatusCode());
}
}
/**
* 恢复逻辑:返回缓存数据
*/
@Recover
public WeatherData recoverGetWeather(
Exception e, String city) {
log.warn("天气API调用失败,使用缓存数据," +
"城市: {}", city, e);
// 返回缓存数据
WeatherData cached =
weatherCache.get(city);
if (cached != null) {
cached.setFromCache(true);
return cached;
}
// 返回默认数据
return WeatherData.createDefault(city);
}
}
@Data
public class WeatherData {
private String city;
private String weather;
private int temperature;
private int humidity;
private LocalDateTime updateTime;
private boolean fromCache;
public static WeatherData
createDefault(String city) {
WeatherData data = new WeatherData();
data.setCity(city);
data.setWeather("未知");
data.setTemperature(0);
data.setHumidity(0);
data.setFromCache(false);
return data;
}
}
九、@Retryable vs RetryTemplate对比
选择维度对比
| 维度 | @Retryable | RetryTemplate |
|---|---|---|
| 学习难度 | 低(注解即可) | 中等(需要理解配置) |
| 代码简洁度 | 高 | 中等 |
| 灵活性 | 有限 | 高 |
| 代码量 | 少 | 多 |
| 性能开销 | AOP代理开销 | 最小 |
| 动态性 | 静态配置 | 可运行时调整 |
| 适用场景 | 简单业务 | 复杂微服务 |
选择建议
选择@Retryable的场景:
// 1. 简单的业务方法,重试逻辑固定
@Service
public class SimpleService {
@Retryable(maxAttempts = 3)
public void simpleMethod() { }
}
// 2. Service层的方法,可以接受AOP代理
@Service // ✅ 可以代理
public class MyService {
@Retryable(maxAttempts = 3)
public void myMethod() { }
}
// 3. 团队偏好声明式编程
// 3. 快速原型开发
选择RetryTemplate的场景:
// 1. 复杂的重试策略,需要运行时动态调整
public void complexRetry() {
String strategy = configService
.getRetryStrategy();
RetryTemplate template =
retryTemplateFactory.create(
strategy);
}
// 2. 对性能有严格要求
// 3. 需要精细控制重试过程
// 4. 在非Service类中使用
// 5. 需要不同的重试策略
@Component
public class MultiStrategyRetry {
@Autowired
@Qualifier("fastRetryTemplate")
private RetryTemplate fastTemplate;
@Autowired
@Qualifier("slowRetryTemplate")
private RetryTemplate slowTemplate;
public void execute(String type) {
if ("fast".equals(type)) {
fastTemplate.execute(callback);
} else {
slowTemplate.execute(callback);
}
}
}
十、总结与展望
10.1 核心要点总结
┌─────────────────────────────────────┐
│ Spring重试机制核心架构 │
├─────────────────────────────────────┤
│ │
│ @Retryable @EnableRetry │
│ ↓ │
│ AOP代理 → 重试拦截器 │
│ ↓ │
│ RetryTemplate 核心类 │
│ ↓ │
│ ┌─────────┬──────────┬──────────┐ │
│ │Retry │BackOff │Recovery │ │
│ │Policy │Policy │Callback │ │
│ └─────────┴──────────┴──────────┘ │
│ ↓ ↓ ↓ │
│ 检查是否 计算延迟 恢复处理 │
│ 需要重试 时间 │
│ │
└─────────────────────────────────────┘
10.2 关键最佳实践回顾
1. ✅ 重试是构建高可用系统的必要组件
- 处理临时故障
- 提高成功率
- 改善用户体验
2. ✅ Spring Retry提供两种重试方式
- 声明式:@Retryable(简单易用)
- 编程式:RetryTemplate(灵活强大)
3. ✅ 指数退避是处理分布式故障的最佳策略
- 防止雪崩
- 给服务恢复时间
- 参数:delay × multiplier^n
4. ✅ 必须确保被重试的操作是幂等的
- 使用业务ID去重
- 幂等检查
- 事务隔离
5. ✅ 结合熔断器构建稳健系统
- 熔断器:快速失败
- 重试机制:恢复故障
- 降级方案:保底策略
6. ✅ 监控和告警至关重要
- 记录重试指标
- 监控成功率
- 设置告警阈值
7. ✅ 合理选择重试异常
- 临时故障:重试
- 业务异常:不重试
- 权限异常:不重试
8. ✅ 设置合理的超时和重试次数
- 根据业务重要性调整
- 快速失败原则
- 防止级联故障
10.3 生产环境检查清单
部署到生产环境前的检查清单:
□ 已添加@EnableRetry到启动类
□ 已添加spring-retry和spring-aspects依赖
□ 重试异常类型配置正确
□ @Recover方法签名与@Retryable匹配
□ 被重试的操作已确认是幂等的
□ 设置了合理的maxAttempts(通常3-5)
□ 配置了指数退避策略(delay+multiplier+maxDelay)
□ 实现了RetryListener进行监控
□ 设置了合理的超时时间
□ 结合了熔断器或限流器
□ 准备了降级方案
□ 添加了详细的日志记录
□ 监控重试指标(成功率、延迟等)
□ 设置了告警阈值
□ 进行了压测验证
□ 编写了重试相关的单元测试
10.4 进阶学习资源
📚 官方文档
- Spring Retry官方文档
- Spring Cloud Resilience4j指南
📖 相关论文和文章
- "Retry Storms and How to Avoid Them"
- AWS Lambda重试最佳实践
- Google SRE Book(第二章:容错性)
🔨 相关开源项目
- Resilience4j(现代化容错库)
- Hystrix(Netflix容错框架)
- Polly(.NET重试库参考)
🎓 推荐课程
- 微服务架构设计
- 分布式系统基础
- 高可用系统设计
📊 相关话题
- Circuit Breaker模式
- Bulkhead模式
- Rate Limiting限流
- Timeout超时控制
10.5 常见陷阱避坑指南
❌ 陷阱1:所有异常都重试
✅ 解决:只重试临时故障
❌ 陷阱2:没有指数退避
✅ 解决:使用exponential backoff
❌ 陷阱3:重试非幂等操作
✅ 解决:检查幂等性或添加去重
❌ 陷阱4:重试次数过多
✅ 解决:通常3-5次足够
❌ 陷阱5:忽视监控
✅ 解决:添加完整的监控指标
❌ 陷阱6:没有降级方案
✅ 解决:实现@Recover和回调
❌ 陷阱7:与熔断器冲突
✅ 解决:正确组合两者
❌ 陷阱8:私有方法无法重试
✅ 解决:改为public方法
10.6 结语
重试机制是分布式系统的关键特性。Spring Framework通过简洁的注解和灵活的模板方法提供了企业级的重试支持。在实际应用中,根据具体场景选择合适的策略,并与其他容错机制结合使用,才能构建真正稳健的系统。
关键是要记住:
好的重试策略 = 正确的异常判断 + 合理的延迟策略 + 幂等性保证 + 监控告警
希望本文能帮助你在分布式系统设计中取得成功!
评论区