在某些時候,我們希望某個同步調用執行更長的時間(異步暫時不考慮),這個時候,首先就是要設置OpenFeign的timeout設定。
下面我們舉例來說明,可以如何設定TimeOut參數。
一、環境
脫離環境說明問題就是流氓。
cloud的版本為 2021.0.0
spring-boot-starter-parent 本本是2.6.2
通過官網和下載的jar包可以看到,OpenFeign有關的版本是3.1.0。
於是我們取看下官網的文檔。
二、概述
1.通過官網的timeout章節,我們可以看到設置timeout至少有兩種方式:通過屬性文件(properties/yml)和配置
2.這個版本的openFeign設計至少是比較合理的
三、代碼
分為四個部分:
1.目標服務CustomerService的list接口
@RequestMapping(value = "/list") @ResponseBody public Object list() throws InterruptedException { // TODO: Thread.sleep(25000); Map<String,Object> customer=new HashMap<String,Object>(); customer.put("name","lzf"); customer.put("sex","男"); customer.put("age","99"); customer.put("address","中華"); return customer; }
以上代碼故意演示25秒。
2.測試服務ConfigServiceTest的屬性文件
feign: client: config: default: connectTimeout: 30000 readTimeout: 30000
connectTimeout--連接超時
readTimeout--讀取超時
二者的區別在於:readTimeout只有成功連接后才會觸發。所以如果連接總是沒有問題,但是執行太久,那么就必須設定readTimeout。
並非二者必須配對使用。
--
注意:default關鍵字標識對所有client生效,如果是想針對某個服務,可以直接寫服務名稱,例如這里可以寫 CustomerService.
3.測試服務ConfigServiceTest的java配置
配置類
public class CustomerServiceFeignInter { @Bean public Integer connectTimeOut() { return 30000; } @Bean public Integer readTimeOut() { return 30000; } @Bean public ErrorDecoder feignErrorDecoder() { return new ErrorHandler(); } @Bean public RequestInterceptor currentUserRequestInterceptor() { return (RequestTemplate template) -> { //Map<String, Collection<String>> header=template.request().headers(); //System.out.println(JSON.toJSONString(header, true)); String token = LoginCache.get(); System.out.println(token); template.header("Authorization", token); }; } }
注:根據關方文檔,config還有另外一種下發,如下:
@Import(FeignClientsConfiguration.class) class FooController { private FooClient fooClient; private FooClient adminClient; @Autowired public FooController(Client client, Encoder encoder, Decoder decoder, Contract contract, MicrometerCapability micrometerCapability) { this.fooClient = Feign.builder().client(client) .encoder(encoder) .decoder(decoder) .contract(contract) .addCapability(micrometerCapability) .requestInterceptor(new BasicAuthRequestInterceptor("user", "user")) .target(FooClient.class, "https://PROD-SVC"); this.adminClient = Feign.builder().client(client) .encoder(encoder) .decoder(decoder) .contract(contract) .addCapability(micrometerCapability) .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin")) .target(FooClient.class, "https://PROD-SVC"); } }
服務接口bean
@FeignClient(value = "CustomerService",configuration= CustomerServiceFeignInter.class) @Component public interface CustomerServiceOpenFeignInterface { @RequestMapping(value = "/customer/list") public Object list(); }
注:根據spring官網的說明,如果有屬性和java配置的時候,屬性處於優先低位,java配置對應部分被無視。
4.測試服務ConfigServiceTest的測試代碼
@RequestMapping(value = "/timeout") @ResponseBody public PublicReturn timeout() { // TODO: try{ Object result=customerService.list(); Map<String,Object> resultMap=new HashMap<>(); resultMap.put("customer",result); PublicReturn re= PublicReturn.getSuccessful("ok",resultMap); return re; } catch (Exception e){ return PublicReturn.getUnSuccessful(e.getMessage()); } }
四、測試和結論
1.如果只是設置屬性,那么可以生效,在超過25秒之后,可以看到返回結果。
{ "flag": 1, "message": "ok", "data": { "customer": { "address": "中華", "sex": "男", "name": "lzf", "age": "99" } } }
2.如果只是設置java配置(即上文的CustomerServiceFeignInter),那么結果同1
3.如果二者都有設置,且屬性的timeOut是5秒,而配置還是30秒,那么結果是屬性勝出,客戶端5秒之后報告異常
feign: client: config: default: connectTimeout: 5000 readTimeout: 5000 compression: request: enabled: true min-request-size: 2048 response: enabled: true
@RequestMapping(value = "/timeout") @ResponseBody public PublicReturn timeout() { // TODO: long start=System.currentTimeMillis(); try{ Object result=customerService.list(); Map<String,Object> resultMap=new HashMap<>(); resultMap.put("customer",result); PublicReturn re= PublicReturn.getSuccessful("ok",resultMap); return re; } catch (Exception e){ long end=System.currentTimeMillis(); return PublicReturn.getUnSuccessful("連接異常"+e.getMessage()+",當前耗費時間"+(end-start)); } }
結果:
{ "flag": 0, "message": "連接異常Read timed out executing GET http://CustomerService/customer/list,當前耗費時間5178", "data": {} }
屬性配置勝出!
五、相干問題-壓縮
如果覺得是因為沒有壓縮導致的timeout或者性能等考慮,那么可以啟動壓縮,盡量避免超時。
具體設置見前文。
六、相關問題-異步
如果實在不想等太久,那么可以考慮采用異步的方式調用。
關於異步的調用,可以參考https://www.jb51.net/article/212227.htm#_label2
@Bean public CustomerServiceOpenFeignInterface originFeignClient(SpringEncoder springEncoder, SpringDecoder springDecoder) { return AsyncFeign.asyncBuilder() .encoder(springEncoder) .decoder(springDecoder) .target(CustomerServiceOpenFeignInterface.class, "http://localhost.charlesproxy.com:8090"); }
測試代碼
@GetMapping("testApi") public String testAsyncClient() throws ExecutionException, InterruptedException { List<CompletableFuture<String>> results = new ArrayList<>(); for(int i = 0; i < 10; i++) { results.add(originFeignClient.api(i+"")); } Thread.sleep(3000); int index = 0; for (CompletableFuture<String> result : results) { String str = result.get(); log.info(String.format("%d, result:%s, ", index, str)); index++; } return "success"; }
或者官網的文檔
七、結論
openFeign越來越完善的情況下,我們傾向於直接使用它的功能。
不過openFeign的一些寫法,我也不是很苟同--例如在破壞原有編碼習慣的情況下,編寫代碼,例如下面的:
public interface UserService { @RequestMapping(method = RequestMethod.GET, value ="/users/{id}") User getUser(@PathVariable("id") long id); } @RestController public class UserResource implements UserService { } package project.user; @FeignClient("users") public interface UserClient extends UserService { }
看起來比較怪異,我個人不是很習慣和接受,已經強烈要求大伙不那么寫。