Feign自定义重试策略及超时时间
背景
feign可以配置重试策略及超时时间,但是无法根据业务场景动态的设置。可能会引起接口幂等,无效重试资源耗费,大数据量耗时操作报超时异常等问题。所以需要更细粒度的重试策略及超时时间配置。
自定义重试策略
框架会使用容器中Retryer
和Request.Options
类型的配置Bean构造对应的feignClient Bean, 后续使用的时候可以直接通过@Autowired
注入即可发起调用;
若要进行更加灵活的控制feign,也可以手动构造FeignClient,通过构造时设置Retryer
和Request.Options
可以达到 feign class 级别控制粒度;
引入全局配置Bean
由于构造FeignClient需要依赖一些Bean,所以先构造全局配置Bean;
@Slf4j
@Configuration
public class FeignAutoConfiguration {
public static final int CONNECT_TIME_OUT_MILLIS = 5000;
public static final int READ_TIME_OUT_MILLIS = 12000;
@Autowired(required = false)
private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();
@Bean
public Encoder encoder(ObjectFactory<HttpMessageConverters> messageConverters) {
Encoder encoder = new SpringEncoder(messageConverters);
return encoder;
}
@Bean
public Decoder decoder(ObjectFactory<HttpMessageConverters> messageConverters) {
Decoder decoder = new SpringDecoder(messageConverters);
return decoder;
}
@Bean
public Contract feignContract(@Qualifier("mvcConversionService") ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}
//全局超时配置
@Bean
public Request.Options options() {
return new Request.Options(CONNECT_TIME_OUT_MILLIS, READ_TIME_OUT_MILLIS);
}
//全局重试策略
@Bean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY;
}
}
手动构造FeignClient
根据上述的配置类,构造自定义FeignClient;
此配置需要在调用方服务中定义,直接复制该配置类,根据需要模仿customRoleClient()
方法实现
// 引入全局配置
@Import(value = {FeignAutoConfiguration.class})
@Configuration
public class CustomFeignClientConfiguration {
@Qualifier("feignClient")
@Autowired
private Client client;
@Autowired
private Encoder encoder;
@Autowired
private Decoder decoder;
@Autowired
private Contract contract;
@Autowired
private Request.Options options;
@Autowired
private Retryer retryer;
/**
* 自定义RoleClient; 【后续扩展自定义Feign的模仿本方法配置即可】
*
* @return
*/
@Bean
public RoleClient customRoleClient() {
//自定义超时时间,connectTimeout 5s ; readTimeout 10s;
Request.Options options = new Request.Options(5, TimeUnit.SECONDS, 10, TimeUnit.SECONDS, true);
//重试2次
Retryer.Default retryer = new Retryer.Default(100, SECONDS.toMillis(1), 2);
return getCustomFeignClient(RoleClient.class, options, retryer);
}
/**
* 手动构建feignClient工具方法
*
* @param clazz
* @param options
* @param retryer
* @param <T>
* @return
*/
private <T> T getCustomFeignClient(Class<T> clazz, Request.Options options, Retryer retryer) {
//只需要对其中的超时和重试配置自定义,其他的还需要使用全局配置
//通过反射获取@FeignClient注解
FeignClient annotation = clazz.getAnnotation(FeignClient.class);
return Feign.builder()
.client(client)
.options(options == null ? this.options : options)
.retryer(retryer == null ? this.retryer : retryer)
.contract(contract)
.encoder(encoder)
.decoder(decoder)
.target(clazz, "http://" + annotation.value());
}
}
使用自定义FeignClient
由于框架会根据全局配置构造一个FeignClientBean, 上述步骤又手动构造了一个Bean,容器中存在两个相同类型RoleClient
的Bean。
使用@Autowired
注入需要添加@Qualifier("customRoleClient")
标识唯一Bean 。
可以使用@Resource
注解,优先根据beanName注入。
// 注入
@Resource
private RoleClient roleClient;
@Resource
private RoleClient customRoleClient;
public void checkRoleDataAuth(String roleId){
// 使用时直接替换feignClient即可
// ResultBody resultBody = roleClient.checkRoleDataAuth(roleId);
ResultBody resultBody = customRoleClient.checkRoleDataAuth(roleId);
if (!resultBody.isSuccess()){
throw new BaseException(resultBody.getCode(),resultBody.getMessage());
}
}
自定义超时时间
在处理大数据量、大文件以、统计等耗时任务时需要自定义超时时间,防止出现feign调用超时异常。
feignClient粒度的自定义超时
根据上文的描述,可以自定义FeignClientBean,从而将超时时间控制在client Bean粒度。
方法粒度的自定义超时
feign方法调用逻辑
feign.SynchronousMethodHandler#invoke
方法源码
//feign方法调用实现
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
//获取当前方法的Request.Options超时配置
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
//方法调用
return executeAndDecode(template, options);
} catch (RetryableException e) {
//重试
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
//从feignClient方法参数列表中找到Request.Options实例对象
Options findOptions(Object[] argv) {
// 如果方法没有参数,使用client配置
if (argv == null || argv.length == 0) {
return this.options;
}
//查找并使用参数列表的Request.Options,若不存在则使用client配置
return Stream.of(argv)
.filter(Options.class::isInstance)
.map(Options.class::cast)
.findFirst()
.orElse(this.options);
}
方法定义
基于以上的代码分析,可以在feign方法签名中参数列表增加一个Request.Options
参数,在调用的时候动态构建Request.Options
对象传入;
@FeignClient(value = UserConstants.SERVER_NAME)
public interface RoleClient {
@GetMapping(value = "/openfeign/role/checkRoleDataAuth")
ResultBody checkRoleDataAuth(@RequestParam("roleId") String roleId, Request.Options options);
}
方法调用
//自定义超时时间,connectTimeout 5s ; readTimeout 60s;
Request.Options options = new Request.Options(5, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true);
ResultBody resultBody = roleClient.checkRoleDataAuth(roleId, options);
//传入null,使用client中的超时配置
ResultBody resultBody = roleClient.checkRoleDataAuth(roleId, null);
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构