news 2026/1/1 7:40:58

Spring Retry 全维度详解(结合 OpenFeign 实战)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Retry 全维度详解(结合 OpenFeign 实战)

目录

一、核心设计理念

二、快速入门:核心依赖与启用

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-retryspring-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. 注解参数详解
注解参数类型默认值作用
@RetryableincludeClass<?>[]{}指定触发重试的异常类型(优先级高于 exclude)
excludeClass<?>[]{}指定不触发重试的异常类型
maxAttemptsint3最大重试次数(不含首次请求)
backoff@Backoff@Backoff()退避策略配置
valueClass<?>[]{}等同于 include,为了简化书写(value = {Exception.class})
labelString""重试标签(用于监控 / 日志)
@BackoffinitialIntervallong1000初始重试间隔(毫秒)
multiplierdouble1.0指数退避倍数(1.0 = 固定间隔,2.0 = 每次间隔翻倍)
maxIntervallong0最大重试间隔(毫秒,0 = 无限制)
delaylong0固定重试间隔(优先级高于 initialInterval,不推荐混用)
randombooleanfalse是否随机调整重试间隔(避免服务峰值)
@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)降级逻辑必须有
  • 重试耗尽后必须通过@RecoverRecoveryCallback提供降级逻辑;
  • 降级逻辑需轻量(无远程调用),避免降级方法又触发异常。

六、常见问题排查

1. 重试不生效

  • 检查是否引入spring-aspects依赖(AOP 核心);
  • 检查启动类是否添加@EnableRetry
  • 检查@Retryableinclude是否包含目标异常类型;
  • Feign 接口的@Recover方法需定义为default方法(否则无法识别)。

2. 降级方法不执行

  • 降级方法参数必须包含「重试方法参数 + 异常类型」;
  • 返回值必须与重试方法一致;
  • 若重试次数未耗尽(如 maxAttempts=2,仅失败 1 次),降级方法不会执行。

3. 重试导致重复数据

  • 立即停止重试,检查请求类型是否为 POST;
  • 对写请求添加幂等性控制(如基于业务唯一键、请求 ID 去重)。

4. 重试次数超出预期

  • 区分maxAttempts的定义:Spring Retry 是「不含首次」,Feign 原生是「含首次」;
  • 检查是否配置了多个重试策略(如全局 + 局部)。

七、总结

Spring Retry 是 OpenFeign 实现可靠重试的最佳选择,其核心价值在于:

  1. 声明式注解(@Retryable)简化重试配置,无侵入;
  2. 灵活的策略(重试次数、退避间隔、异常类型)适配不同场景;
  3. 完善的降级机制(@Recover)保证服务可用性;
  4. 可与熔断、监控集成,适配生产环境。

使用时需牢记:重试是兜底手段,核心还是要提升服务本身的稳定性;重试必须配合幂等性和降级,否则会引发新的问题

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/17 10:58:44

开源TTS模型选型指南:为何EmotiVoice脱颖而出?

开源TTS模型选型指南&#xff1a;为何EmotiVoice脱颖而出&#xff1f; 在智能语音技术飞速发展的今天&#xff0c;我们早已不满足于“能说话”的AI。从车载助手到虚拟偶像&#xff0c;用户期待的是有情绪、有个性、像真人一样的声音。然而&#xff0c;大多数开源文本转语音&…

作者头像 李华
网站建设 2025/12/29 5:24:15

React RSC 新漏洞可导致 DoS 和源代码泄露

聚焦源代码安全&#xff0c;网罗国内外最新资讯&#xff01;编译&#xff1a;代码卫士React团队修复了React服务器组件&#xff08;RSC&#xff09;中的两个新漏洞&#xff0c;如遭成功利用&#xff0c;可能导致拒绝服务&#xff08;DoS&#xff09;或源代码泄露。React 团队表…

作者头像 李华
网站建设 2025/12/17 10:57:39

【大模型微调】11-Prefix Tuning技术:分析Prefix Tuning的工作机制

引言Prefix Tuning技术是近年来在自然语言处理&#xff08;NLP&#xff09;领域崭露头角的一种创新方法。作为一种高效的模型微调技术&#xff0c;Prefix Tuning旨在通过在输入序列前添加可学习的"前缀"&#xff08;prefix&#xff09;来调整预训练语言模型的性能&am…

作者头像 李华
网站建设 2025/12/24 6:55:53

低版本ant design vue 实现年度选择器

"ant-design-vue": "^1.6.5", 实现年度选择器, 绕了一大圈才找到解决办法,特在此记录 <a-form-item class"formItem_style"><span class"form-item-title">年度</span><a-date-picker v-model"searchForm.…

作者头像 李华
网站建设 2025/12/17 10:57:22

Cursor Rule:AI如何革新代码导航与智能提示

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个基于Cursor Rule的智能代码导航插件&#xff0c;要求&#xff1a;1. 支持通过自然语言描述跳转到指定代码段&#xff08;如跳转到用户登录验证逻辑&#xff09;2. 根据当前…

作者头像 李华
网站建设 2025/12/25 15:13:04

SGLang AI 金融 π 对(杭州站)火热来袭!

本次 SGLang Meetup 聚焦于提升大模型推理效率的核心工程挑战。活动将介绍 SGLang 创新的缓存系统&#xff0c;通过分层与混合架构管理来优化内存使用&#xff1b;分享能实现模型权重秒级更新与快速加载的中间件&#xff0c;以加速强化学习等场景的迭代。同时&#xff0c;社区也…

作者头像 李华