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 {

}

 

  看起来比较怪异,我个人不是很习惯和接受,已经强烈要求大伙不那么写。

 

posted @ 2022-02-28 20:42  正在战斗中  阅读(871)  评论(0编辑  收藏  举报