目录
一、核心设计理念
二、快速入门:核心依赖与启用
1. 引入依赖
2. 启用 Spring Retry
三、核心使用方式
方式 1:声明式重试(注解方式,推荐)
1. 基础示例(结合 OpenFeign)
2. 注解参数详解
方式 2:编程式重试(手动控制)
1. 配置 RetryTemplate Bean
2. 编程式调用示例
方式 3:全局默认配置
四、高级特性
1. 重试监听(监控重试过程)
2. 条件重试(按返回值重试)
3. 与熔断组件集成(Sentinel/Hystrix)
五、生产环境最佳实践
1. 核心配置建议
2. 关键注意事项
(1)严格区分幂等请求
(2)重试与超时的配合
(3)避免重试叠加
(4)日志与监控
(5)降级逻辑必须有
六、常见问题排查
1. 重试不生效
2. 降级方法不执行
3. 重试导致重复数据
4. 重试次数超出预期
七、总结
Spring Retry 是 Spring 生态中用于声明式 / 编程式重试的框架,基于 AOP 实现,支持灵活的重试策略、退避策略、降级处理,是生产环境中实现可靠重试的首选方案。本文从核心概念、使用方式、高级特性、与 OpenFeign 集成等维度,全面讲解 Spring Retry 的设计与实践。
一、核心设计理念
Spring Retry 的核心目标是对不稳定的操作(如网络调用、数据库访问)进行容错处理,通过重试降低临时故障的影响,同时提供降级机制保证系统可用性。其核心设计包含 3 个核心组件:
| 组件 | 作用 |
|---|---|
| 重试策略(RetryPolicy) | 决定「何时重试」(如异常类型、重试次数、返回值条件) |
| 退避策略(BackOffPolicy) | 决定「重试间隔」(如固定间隔、指数退避、随机间隔) |
| 重试模板(RetryTemplate) | 重试的核心执行器,整合重试策略和退避策略,提供统一的重试入口 |
二、快速入门:核心依赖与启用
1. 引入依赖
Spring Retry 依赖 AOP 实现,需同时引入spring-retry和spring-aspects:
xml
<!-- Maven 依赖 --> <dependencies> <!-- Spring Retry 核心 --> <dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> <version>1.3.4</version> <!-- 适配 Spring Boot 2.x,3.x 可使用 2.x 版本 --> </dependency> <!-- AOP 依赖(必须) --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.30</version> </dependency> </dependencies>2. 启用 Spring Retry
在 Spring Boot 启动类添加@EnableRetry注解,开启重试功能:
java
运行
import org.springframework.retry.annotation.EnableRetry; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @EnableRetry // 启用重试(核心注解) public class RetryDemoApplication { public static void main(String[] args) { SpringApplication.run(RetryDemoApplication.class, args); } }三、核心使用方式
方式 1:声明式重试(注解方式,推荐)
通过@Retryable、@Recover注解快速实现重试,无需侵入业务代码,是最常用的方式。
1. 基础示例(结合 OpenFeign)
java
运行
import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; import org.springframework.retry.annotation.Recover; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient(name = "product-service", url = "${product.service.url}") public interface ProductFeignClient { /** * 声明式重试配置: * - 仅对指定异常重试(网络超时、5xx 错误) * - 最大重试 2 次(首次请求 + 2 次重试 = 共 3 次) * - 重试间隔:初始 1 秒,指数退避(第 2 次重试间隔 2 秒) */ @Retryable( // 触发重试的异常类型(数组) include = { java.net.SocketTimeoutException.class, // 网络超时 feign.RetryableException.class, // Feign 重试异常 org.springframework.web.client.HttpServerErrorException.class // 5xx 错误 }, // 排除不重试的异常 exclude = { org.springframework.web.client.HttpClientErrorException.class // 4xx 客户端错误 }, // 最大重试次数(不含首次请求,默认 3) maxAttempts = 2, // 退避策略:initialInterval 初始间隔(毫秒),multiplier 指数倍数 backoff = @Backoff(initialInterval = 1000, multiplier = 2, maxInterval = 3000) ) @GetMapping("/product/{id}") ProductDTO getProductById(@PathVariable("id") Long id); /** * 重试耗尽后的降级方法(核心!避免重试失败后直接抛异常) * 规则: * 1. 方法名任意,需添加 @Recover 注解; * 2. 参数必须包含:重试方法的所有参数 + 触发重试的异常类型(顺序可调整); * 3. 返回值必须与重试方法一致; * 4. 若为 Feign 接口,需定义为 default 方法(Java 8+ 支持)。 */ @Recover default ProductDTO recoverProduct(Exception e, Long id) { // 降级逻辑:返回默认值、空值,或记录告警日志 System.err.printf("获取商品失败,id:%d,异常:%s%n", id, e.getMessage()); return new ProductDTO().setId(id).setName("默认商品").setPrice(0.0); } }2. 注解参数详解
| 注解 | 参数 | 类型 | 默认值 | 作用 |
|---|---|---|---|---|
@Retryable | include | Class<?>[] | {} | 指定触发重试的异常类型(优先级高于 exclude) |
exclude | Class<?>[] | {} | 指定不触发重试的异常类型 | |
maxAttempts | int | 3 | 最大重试次数(不含首次请求) | |
backoff | @Backoff | @Backoff() | 退避策略配置 | |
value | Class<?>[] | {} | 等同于 include,为了简化书写(value = {Exception.class}) | |
label | String | "" | 重试标签(用于监控 / 日志) | |
@Backoff | initialInterval | long | 1000 | 初始重试间隔(毫秒) |
multiplier | double | 1.0 | 指数退避倍数(1.0 = 固定间隔,2.0 = 每次间隔翻倍) | |
maxInterval | long | 0 | 最大重试间隔(毫秒,0 = 无限制) | |
delay | long | 0 | 固定重试间隔(优先级高于 initialInterval,不推荐混用) | |
random | boolean | false | 是否随机调整重试间隔(避免服务峰值) | |
@Recover | 无 | - | - | 标记重试耗尽后的降级方法 |
方式 2:编程式重试(手动控制)
通过RetryTemplate手动构建重试逻辑,灵活性更高,适合需要动态调整重试策略的场景。
1. 配置 RetryTemplate Bean
java
运行
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.retry.backoff.ExponentialBackOffPolicy; import org.springframework.retry.policy.CompositeRetryPolicy; import org.springframework.retry.policy.SimpleRetryPolicy; import org.springframework.retry.policy.TimeoutRetryPolicy; import org.springframework.retry.support.RetryTemplate; @Configuration public class RetryTemplateConfig { /** * 自定义重试模板:支持组合策略(次数限制 + 超时限制) */ @Bean public RetryTemplate feignRetryTemplate() { RetryTemplate retryTemplate = new RetryTemplate(); // 1. 重试策略:组合策略(次数 + 超时) CompositeRetryPolicy compositeRetryPolicy = new CompositeRetryPolicy(); // 1.1 次数策略:最大重试 2 次,仅对指定异常重试 SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy(); simpleRetryPolicy.setMaxAttempts(2); // 重试次数(不含首次) // 指定可重试的异常(key=异常类型,value=true=可重试) simpleRetryPolicy.setRetryableExceptions( java.net.SocketTimeoutException.class, true, feign.RetryableException.class, true ); // 指定不可重试的异常 simpleRetryPolicy.setFatalExceptions( org.springframework.web.client.HttpClientErrorException.class ); // 1.2 超时策略:重试总耗时不超过 5 秒 TimeoutRetryPolicy timeoutRetryPolicy = new TimeoutRetryPolicy(); timeoutRetryPolicy.setTimeout(5000); // 毫秒 compositeRetryPolicy.setPolicies(new SimpleRetryPolicy[]{simpleRetryPolicy, timeoutRetryPolicy}); retryTemplate.setRetryPolicy(compositeRetryPolicy); // 2. 退避策略:指数退避 ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); backOffPolicy.setInitialInterval(1000); // 初始间隔 1 秒 backOffPolicy.setMaxInterval(3000); // 最大间隔 3 秒 backOffPolicy.setMultiplier(2); // 间隔翻倍(1s → 2s → 3s) retryTemplate.setBackOffPolicy(backOffPolicy); return retryTemplate; } }2. 编程式调用示例
java
运行
import org.springframework.retry.support.RetryTemplate; import org.springframework.stereotype.Service; import javax.annotation.Resource; @Service public class ProductService { @Resource private ProductFeignClient productFeignClient; @Resource private RetryTemplate feignRetryTemplate; /** * 编程式重试调用 Feign 接口 */ public ProductDTO getProduct(Long id) { try { // execute 方法: // - 参数1:RetryCallback(重试逻辑) // - 参数2:RecoveryCallback(降级逻辑,可选) return feignRetryTemplate.execute( // 重试逻辑:调用 Feign 接口 context -> { System.out.printf("第 %d 次调用商品接口,id:%d%n", context.getRetryCount() + 1, id); return productFeignClient.getProductById(id); }, // 降级逻辑:重试耗尽后执行 context -> { System.err.printf("重试耗尽,id:%d,失败次数:%d%n", id, context.getRetryCount()); return new ProductDTO().setId(id).setName("降级商品"); } ); } catch (Exception e) { // 兜底异常处理 System.err.println("获取商品异常:" + e.getMessage()); return new ProductDTO(); } } }方式 3:全局默认配置
通过@Retryable的默认属性或自定义RetryTemplate实现全局重试规则,避免重复配置。
java
运行
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.retry.annotation.Retryable; import org.springframework.retry.annotation.RetryConfiguration; // 全局重试配置(覆盖默认的 RetryConfiguration) @Configuration public class GlobalRetryConfig extends RetryConfiguration { /** * 全局默认 RetryTemplate */ @Override @Bean public RetryTemplate retryTemplate() { RetryTemplate retryTemplate = new RetryTemplate(); // 配置全局重试策略(参考方式2) return retryTemplate; } /** * 全局 @Retryable 注解的默认属性(可选) */ @Bean public Retryable defaultRetryable() { // 通过注解属性设置全局默认值 return new Retryable() { @Override public Class<? extends Annotation> annotationType() { return Retryable.class; } @Override public Class<?>[] include() { return new Class[]{feign.RetryableException.class}; } @Override public int maxAttempts() { return 2; } // 其他属性默认值... }; } }四、高级特性
1. 重试监听(监控重试过程)
通过RetryListener监听重试的开始、异常、完成事件,用于日志记录、监控告警:
java
运行
import org.springframework.retry.RetryCallback; import org.springframework.retry.RetryContext; import org.springframework.retry.RetryListener; import org.springframework.stereotype.Component; @Component public class CustomRetryListener implements RetryListener { /** * 重试开始前触发 */ @Override public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) { System.out.println("重试开始,上下文:" + context); return true; // 返回 true 继续重试,false 终止 } /** * 每次重试异常时触发 */ @Override public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) { System.err.printf("重试失败,次数:%d,异常:%s%n", context.getRetryCount(), throwable.getMessage()); // 可在这里接入告警(如钉钉、短信) } /** * 重试结束后触发(成功/失败) */ @Override public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) { if (throwable == null) { System.out.println("重试成功,总次数:" + context.getRetryCount()); } else { System.err.println("重试耗尽,总次数:" + context.getRetryCount()); } } }2. 条件重试(按返回值重试)
默认重试仅基于异常,若需按返回值重试(如返回 null、空列表),需自定义RetryPolicy:
java
运行
import org.springframework.retry.RetryContext; import org.springframework.retry.policy.AbstractRetryPolicy; import org.springframework.stereotype.Component; @Component public class ReturnValueRetryPolicy extends AbstractRetryPolicy { @Override public boolean canRetry(RetryContext context) { // 获取方法返回值 Object result = context.getAttribute("result"); // 条件:返回 null 则重试 return result == null && context.getRetryCount() < 2; } @Override public void registerThrowable(RetryContext context, Throwable throwable) { super.registerThrowable(context, throwable); } @Override public RetryContext open(RetryContext parent) { return new DefaultRetryContext(parent, this); } }3. 与熔断组件集成(Sentinel/Hystrix)
生产环境中,重试需与熔断配合,避免重试导致服务雪崩:
java
运行
import com.alibaba.csp.sentinel.annotation.SentinelResource; import org.springframework.retry.annotation.Retryable; @FeignClient(name = "order-service") public interface OrderFeignClient { @Retryable(maxAttempts = 1, backoff = @Backoff(initialInterval = 1000)) @SentinelResource( value = "getOrder", fallback = "fallbackGetOrder", // 熔断降级 blockHandler = "blockGetOrder" // 流控降级 ) @GetMapping("/order/{id}") OrderDTO getOrderById(@PathVariable("id") Long id); // 熔断降级方法 default OrderDTO fallbackGetOrder(Long id) { return new OrderDTO(); } // 流控降级方法 default OrderDTO blockGetOrder(Long id, com.alibaba.csp.sentinel.slots.block.BlockException e) { return new OrderDTO(); } }五、生产环境最佳实践
1. 核心配置建议
| 场景 | 重试次数 | 重试间隔 | 触发条件 | 降级策略 |
|---|---|---|---|---|
| 核心服务(如订单) | 1 次 | 1 秒(固定) | 5xx / 超时 | 返回默认值 + 告警 |
| 非核心服务(如商品) | 2 次 | 1s → 2s(指数) | 5xx / 超时 / 网络异常 | 返回空值 + 日志 |
| 外部接口(如支付) | 0-1 次 | 2 秒(固定) | 仅 503 / 超时 | 人工重试 + 降级提示 |
2. 关键注意事项
(1)严格区分幂等请求
- ✅ 允许重试:GET(查询)、PUT(更新)、DELETE(幂等删除);
- ❌ 禁止重试:POST(创建)、PATCH(部分更新)(除非做了幂等性设计,如请求 ID 去重)。
(2)重试与超时的配合
Feign 超时时间需包含单次请求超时 + 重试总耗时:
yaml
# application.yml feign: client: config: default: connectTimeout: 1000 # 连接超时 1 秒 readTimeout: 3000 # 单次读取超时 3 秒 # 重试 2 次 → 总超时 = 3*(2+1) = 9 秒,需确保业务超时 > 9 秒(3)避免重试叠加
- 禁止同时使用「Feign 原生 Retryer」和「Spring Retry」,否则会导致重复重试;
- 若使用 Spring Retry,需禁用 Feign 原生重试:
java
运行
@Bean public Retryer feignRetryer() { return Retryer.NEVER_RETRY; // 禁用 Feign 原生重试 }
(4)日志与监控
- 记录每次重试的原因、次数、耗时(便于排查问题);
- 对重试次数超过阈值的接口告警(如 1 分钟内重试 > 10 次);
- 监控重试成功率,作为服务稳定性的指标。
(5)降级逻辑必须有
- 重试耗尽后必须通过
@Recover或RecoveryCallback提供降级逻辑; - 降级逻辑需轻量(无远程调用),避免降级方法又触发异常。
六、常见问题排查
1. 重试不生效
- 检查是否引入
spring-aspects依赖(AOP 核心); - 检查启动类是否添加
@EnableRetry; - 检查
@Retryable的include是否包含目标异常类型; - Feign 接口的
@Recover方法需定义为default方法(否则无法识别)。
2. 降级方法不执行
- 降级方法参数必须包含「重试方法参数 + 异常类型」;
- 返回值必须与重试方法一致;
- 若重试次数未耗尽(如 maxAttempts=2,仅失败 1 次),降级方法不会执行。
3. 重试导致重复数据
- 立即停止重试,检查请求类型是否为 POST;
- 对写请求添加幂等性控制(如基于业务唯一键、请求 ID 去重)。
4. 重试次数超出预期
- 区分
maxAttempts的定义:Spring Retry 是「不含首次」,Feign 原生是「含首次」; - 检查是否配置了多个重试策略(如全局 + 局部)。
七、总结
Spring Retry 是 OpenFeign 实现可靠重试的最佳选择,其核心价值在于:
- 声明式注解(
@Retryable)简化重试配置,无侵入; - 灵活的策略(重试次数、退避间隔、异常类型)适配不同场景;
- 完善的降级机制(
@Recover)保证服务可用性; - 可与熔断、监控集成,适配生产环境。
使用时需牢记:重试是兜底手段,核心还是要提升服务本身的稳定性;重试必须配合幂等性和降级,否则会引发新的问题。