SpringRetry重试机制

SpringRetry重试机制

Spring Retry 提供了声明式和编程式的重试机制,用于处理暂时性故障(如网络超时、数据库锁等)。

引入依赖

<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>

启用重试

@Configuration
@EnableRetry
public class RetryConfig {
}

注解式重试

@Retryable

@Service
public class RemoteService {

private static final Logger log = LoggerFactory.getLogger(RemoteService.class);

// 基本重试:遇到异常重试3次
@Retryable(retryFor = {RemoteAccessException.class}, maxAttempts = 3)
public String callRemoteApi() {
log.info("调用远程API...");
// 可能抛出异常的调用
return restTemplate.getForObject("https://api.example.com/data", String.class);
}

// 带退避策略:重试间隔递增
@Retryable(
retryFor = {RemoteAccessException.class},
maxAttempts = 5,
backoff = @Backoff(delay = 1000, multiplier = 2, maxDelay = 10000)
)
public String callWithBackoff() {
log.info("调用远程API(带退避)...");
return restTemplate.getForObject("https://api.example.com/data", String.class);
}

// 指定异常类型
@Retryable(
retryFor = {IOException.class, TimeoutException.class},
noRetryFor = {IllegalArgumentException.class},
maxAttempts = 3
)
public String callWithExceptionFilter() {
return restTemplate.getForObject("https://api.example.com/data", String.class);
}

// 根据返回结果重试
@Retryable(
retryFor = {RemoteAccessException.class},
maxAttempts = 3,
recover = "fallback"
)
public String callWithRecover() {
String result = restTemplate.getForObject("https://api.example.com/data", String.class);
if (result == null || result.isEmpty()) {
throw new RemoteAccessException("空结果");
}
return result;
}

// 恢复方法
@Recover
public String fallback(RemoteAccessException e) {
log.warn("远程调用失败,使用降级策略: {}", e.getMessage());
return "default_data";
}
}

参数说明

参数 说明
retryFor 触发重试的异常
noRetryFor 不触发重试的异常
maxAttempts 最大重试次数(含第一次调用)
backoff 退避策略
recover 降级方法名

@Backoff 参数

参数 说明
delay 初始延迟(毫秒)
multiplier 延迟倍数
maxDelay 最大延迟
random 是否随机化延迟
// 固定间隔:1s, 1s, 1s
@Backoff(delay = 1000)

// 指数退避:1s, 2s, 4s, 8s...
@Backoff(delay = 1000, multiplier = 2)

// 指数退避+上限:1s, 2s, 4s, 4s, 4s...
@Backoff(delay = 1000, multiplier = 2, maxDelay = 4000)

// 随机退避:delay ± random%
@Backoff(delay = 1000, random = true)

@CircuitBreaker(熔断器)

@Service
public class CircuitBreakerService {

@Retryable(
retryFor = {RemoteAccessException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000)
)
@CircuitBreaker(
retryFor = {RemoteAccessException.class},
maxAttempts = 3,
openTimeout = 15000, // 15秒内失败3次则熔断
resetTimeout = 30000 // 30秒后尝试恢复
)
public String callWithCircuitBreaker() {
return restTemplate.getForObject("https://api.example.com/data", String.class);
}

@Recover
public String fallback(RemoteAccessException e) {
return "circuit_breaker_fallback";
}
}

编程式重试

@Service
public class ProgrammaticRetryService {

@Autowired
private RetryTemplate retryTemplate;

public String callWithRetryTemplate() {
return retryTemplate.execute(
context -> {
System.out.println("第 " + (context.getRetryCount() + 1) + " 次尝试");
return restTemplate.getForObject("https://api.example.com/data", String.class);
},
context -> {
System.out.println("重试耗尽,执行恢复逻辑");
return "fallback_data";
}
);
}
}

自定义 RetryTemplate

@Bean
public RetryTemplate retryTemplate() {
RetryTemplate template = new RetryTemplate();

// 重试策略:最多3次
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(3,
Collections.singletonMap(RemoteAccessException.class, true));
template.setRetryPolicy(retryPolicy);

// 退避策略:指数退避
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(1000);
backOffPolicy.setMultiplier(2);
backOffPolicy.setMaxInterval(10000);
template.setBackOffPolicy(backOffPolicy);

// 监听器
template.registerListener(new RetryListener() {
@Override
public <T, E extends Throwable> boolean open(RetryContext context,
RetryCallback<T, E> callback) {
System.out.println("开始重试: " + context.getRetryCount());
return true;
}

@Override
public <T, E extends Throwable> void close(RetryContext context,
RetryCallback<T, E> callback,
Throwable throwable) {
System.out.println("重试结束");
}

@Override
public <T, E extends Throwable> void onError(RetryContext context,
RetryCallback<T, E> callback,
Throwable throwable) {
System.out.println("第 " + context.getRetryCount() + " 次重试失败: " + throwable.getMessage());
}
});

return template;
}

监听器

@Component
public class LogRetryListener implements RetryListener {

private static final Logger log = LoggerFactory.getLogger(LogRetryListener.class);

@Override
public <T, E extends Throwable> boolean open(RetryContext context,
RetryCallback<T, E> callback) {
log.info("开始执行: {}, 重试次数: {}",
context.getAttribute("context.name"),
context.getRetryCount());
return true;
}

@Override
public <T, E extends Throwable> void onError(RetryContext context,
RetryCallback<T, E> callback,
Throwable throwable) {
log.warn("执行失败,第 {} 次重试, 异常: {}",
context.getRetryCount(), throwable.getMessage());
}

@Override
public <T, E extends Throwable> void close(RetryContext context,
RetryCallback<T, E> callback,
Throwable throwable) {
if (throwable != null) {
log.error("所有重试都失败了: {}", throwable.getMessage());
} else {
log.info("执行成功,共重试 {} 次", context.getRetryCount());
}
}
}
@Configuration
public class RetryConfig {

@Bean
public RetryTemplate retryTemplate(LogRetryListener listener) {
RetryTemplate template = new RetryTemplate();
template.registerListener(listener);
return template;
}
}

最佳实践

场景 建议
网络超时 使用指数退避,最大延迟 10-30s
数据库死锁 少量重试(2-3次),固定短间隔
第三方 API 熔断器 + 降级
消息消费 根据消息重要性决定重试策略
重试条件 只重试暂时性错误,不重试业务错误

注意事项

  1. 幂等性:重试的方法必须是幂等的
  2. 事务:重试可能导致事务问题,需谨慎
  3. 日志:记录重试次数和异常,便于排查
  4. 降级:始终提供降级方案

总结

Spring Retry 提供了灵活的重试机制:

  • 声明式:@Retryable 简单方便
  • 编程式:RetryTemplate 灵活控制
  • 熔断器:@CircuitBreaker 防止雪崩
  • 监听器:观察重试过程

合理使用重试,可以提高系统的容错能力。


   转载规则


《SpringRetry重试机制》 小乐 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录