Feign自定義重試策略及超時(shí)時(shí)間詳解
背景
feign可以配置重試策略及超時(shí)時(shí)間,但是無法根據(jù)業(yè)務(wù)場(chǎng)景動(dòng)態(tài)的設(shè)置??赡軙?huì)引起接口冪等,無效重試資源耗費(fèi),大數(shù)據(jù)量耗時(shí)操作報(bào)超時(shí)異常等問題。所以需要更細(xì)粒度的重試策略及超時(shí)時(shí)間配置。
自定義重試策略
框架會(huì)使用容器中Retryer
和Request.Options
類型的配置Bean構(gòu)造對(duì)應(yīng)的feignClient Bean, 后續(xù)使用的時(shí)候可以直接通過@Autowired
注入即可發(fā)起調(diào)用;
若要進(jìn)行更加靈活的控制feign,也可以手動(dòng)構(gòu)造FeignClient,通過構(gòu)造時(shí)設(shè)置Retryer
和Request.Options
可以達(dá)到 feign class 級(jí)別控制粒度;
引入全局配置Bean
由于構(gòu)造FeignClient需要依賴一些Bean,所以先構(gòu)造全局配置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); } //全局超時(shí)配置 @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; } }
手動(dòng)構(gòu)造FeignClient
根據(jù)上述的配置類,構(gòu)造自定義FeignClient;
此配置需要在調(diào)用方服務(wù)中定義,直接復(fù)制該配置類,根據(jù)需要模仿customRoleClient()
方法實(shí)現(xiàn)
// 引入全局配置 @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; 【后續(xù)擴(kuò)展自定義Feign的模仿本方法配置即可】 * * @return */ @Bean public RoleClient customRoleClient() { //自定義超時(shí)時(shí)間,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); } /** * 手動(dòng)構(gòu)建feignClient工具方法 * * @param clazz * @param options * @param retryer * @param <T> * @return */ private <T> T getCustomFeignClient(Class<T> clazz, Request.Options options, Retryer retryer) { //只需要對(duì)其中的超時(shí)和重試配置自定義,其他的還需要使用全局配置 //通過反射獲取@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
由于框架會(huì)根據(jù)全局配置構(gòu)造一個(gè)FeignClientBean, 上述步驟又手動(dòng)構(gòu)造了一個(gè)Bean,容器中存在兩個(gè)相同類型RoleClient
的Bean。
使用@Autowired
注入需要添加@Qualifier("customRoleClient")
標(biāo)識(shí)唯一Bean 。
可以使用@Resource
注解,優(yōu)先根據(jù)beanName注入。
// 注入 @Resource private RoleClient roleClient; @Resource private RoleClient customRoleClient; public void checkRoleDataAuth(String roleId){ // 使用時(shí)直接替換feignClient即可 // ResultBody resultBody = roleClient.checkRoleDataAuth(roleId); ResultBody resultBody = customRoleClient.checkRoleDataAuth(roleId); if (!resultBody.isSuccess()){ throw new BaseException(resultBody.getCode(),resultBody.getMessage()); } }
自定義超時(shí)時(shí)間
在處理大數(shù)據(jù)量、大文件以、統(tǒng)計(jì)等耗時(shí)任務(wù)時(shí)需要自定義超時(shí)時(shí)間,防止出現(xiàn)feign調(diào)用超時(shí)異常。
feignClient粒度的自定義超時(shí)
根據(jù)上文的描述,可以自定義FeignClientBean,從而將超時(shí)時(shí)間控制在client Bean粒度。
方法粒度的自定義超時(shí)
feign方法調(diào)用邏輯
feign.SynchronousMethodHandler#invoke
方法源碼
//feign方法調(diào)用實(shí)現(xiàn) @Override public Object invoke(Object[] argv) throws Throwable { RequestTemplate template = buildTemplateFromArgs.create(argv); //獲取當(dāng)前方法的Request.Options超時(shí)配置 Options options = findOptions(argv); Retryer retryer = this.retryer.clone(); while (true) { try { //方法調(diào)用 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方法參數(shù)列表中找到Request.Options實(shí)例對(duì)象 Options findOptions(Object[] argv) { // 如果方法沒有參數(shù),使用client配置 if (argv == null || argv.length == 0) { return this.options; } //查找并使用參數(shù)列表的Request.Options,若不存在則使用client配置 return Stream.of(argv) .filter(Options.class::isInstance) .map(Options.class::cast) .findFirst() .orElse(this.options); }
方法定義
基于以上的代碼分析,可以在feign方法簽名中參數(shù)列表增加一個(gè)Request.Options
參數(shù),在調(diào)用的時(shí)候動(dòng)態(tài)構(gòu)建Request.Options
對(duì)象傳入;
@FeignClient(value = UserConstants.SERVER_NAME) public interface RoleClient { @GetMapping(value = "/openfeign/role/checkRoleDataAuth") ResultBody checkRoleDataAuth(@RequestParam("roleId") String roleId, Request.Options options); }
方法調(diào)用
//自定義超時(shí)時(shí)間,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中的超時(shí)配置 ResultBody resultBody = roleClient.checkRoleDataAuth(roleId, null);
以上就是Feign自定義重試策略及超時(shí)時(shí)間詳解的詳細(xì)內(nèi)容,更多關(guān)于Feign自定義重試策略超時(shí)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
如何解決java.util.concurrent.CancellationException問題
這篇文章主要介紹了如何解決java.util.concurrent.CancellationException問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05SpringBoot發(fā)送郵件功能 驗(yàn)證碼5分鐘過期
這篇文章主要為大家詳細(xì)介紹了SpringBoot發(fā)送郵件功能,驗(yàn)證碼5分鐘過期,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03SpringBoot整合Netty+Websocket實(shí)現(xiàn)消息推送的示例代碼
WebSocket使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡(jiǎn)單,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù),本文主要介紹了SpringBoot整合Netty+Websocket實(shí)現(xiàn)消息推送的示例代碼,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01Spring?Boot簡(jiǎn)單實(shí)現(xiàn)文件上傳功能
這篇文章主要介紹了Spring?Boot簡(jiǎn)單實(shí)現(xiàn)文件上傳功能,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-08-08解決IDEA中下載free maven plugin插件無效的問題
這篇文章主要介紹了解決IDEA中下載free maven plugin插件無效的問題,本文通過圖文并茂的形式給大家分享解決方案,供大家參考,需要的朋友可以參考下2020-11-11Java中生成隨機(jī)數(shù)的實(shí)現(xiàn)方法總結(jié)
這篇文章主要介紹了Java中生成隨機(jī)數(shù)的實(shí)現(xiàn)方法總結(jié),其中多線程并發(fā)的實(shí)現(xiàn)方式尤為exciting,需要的朋友可以參考下2015-11-11帶你用Java方法輕松實(shí)現(xiàn)樹的同構(gòu)
給定兩棵樹T1和T2。如果T1可以通過若干次左右孩子互換就變成T2,則我們稱兩棵樹是“同構(gòu)”的。例如圖1給出的兩棵樹就是同構(gòu)的,因?yàn)槲覀儼哑渲幸豢脴涞慕Y(jié)點(diǎn)A、B、G的左右孩子互換后,就得到另外一棵樹2021-06-06IDEA中如何查找jar包之間的依賴關(guān)系并忽略依賴的某個(gè)包
這篇文章主要介紹了IDEA中如何查找jar包之間的依賴關(guān)系并忽略依賴的某個(gè)包?本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08