SpringCloud开发之OpenFeign timeout和压缩等问题
在某些时候,我们希望某个同步调用执行更长的时间(异步暂时不考虑),这个时候,首先就是要设置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 { }
看起来比较怪异,我个人不是很习惯和接受,已经强烈要求大伙不那么写。