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 {

}

 

  看起來比較怪異,我個人不是很習慣和接受,已經強烈要求大伙不那么寫。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM