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);
以上就是Feign自定义重试策略及超时时间详解的详细内容,更多关于Feign自定义重试策略超时的资料请关注脚本之家其它相关文章!