@FeignClient注解配置局部超時時間、OkHttp長連接和SocketTimeoutException異常解決辦法


問題描述:open feign配置OKhttp調用遠程API,連續調用次數較少時,一切正常,次數非常多時(例如,連續請求600次)就拋出java.net.SocketTimeoutException: timeout,關鍵信息如下:

Caused by: java.net.SocketTimeoutException: timeout
  at okhttp3.internal.http2.Http2Stream$StreamTimeout.newTimeoutException(Http2Stream.kt:662)
  at okhttp3.internal.http2.Http2Stream$StreamTimeout.exitAndThrowIfTimedOut(Http2Stream.kt:671)
  at okhttp3.internal.http2.Http2Stream$FramingSource.read(Http2Stream.kt:377)
  at okhttp3.internal.connection.Exchange$ResponseBodySource.read(Exchange.kt:279)
  at okio.RealBufferedSource.read(RealBufferedSource.kt:41)
  at okio.RealBufferedSource.exhausted(RealBufferedSource.kt:51)
  at okio.InflaterSource.refill(InflaterSource.kt:94)
  at okio.InflaterSource.read(InflaterSource.kt:54)
  at okio.GzipSource.read(GzipSource.kt:69)
  at okio.RealBufferedSource$inputStream$1.read(RealBufferedSource.kt:438)
  at java.io.FilterInputStream.read(FilterInputStream.java:133)
  at java.io.PushbackInputStream.read(PushbackInputStream.java:186)
  at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
  at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
  at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
  at java.io.InputStreamReader.read(InputStreamReader.java:184)
  at java.io.Reader.read(Reader.java:140)
  at org.springframework.util.StreamUtils.copyToString(StreamUtils.java:91)
  at org.springframework.http.converter.StringHttpMessageConverter.readInternal(StringHttpMessageConverter.java:96)
  at org.springframework.http.converter.StringHttpMessageConverter.readInternal(StringHttpMessageConverter.java:44)
  at org.springframework.http.converter.AbstractHttpMessageConverter.read(AbstractHttpMessageConverter.java:199)
  at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:114)
  ... 15 common frames omitted

使用spring java config 進行局部屬性配置,OKhttp基本配置代碼如下所示:

/**
 * @author 樓蘭胡楊
 */
public class FeignConfig {

    private final static int READ_TIMEOUT = 10;
    private final static int MAX_IDLE_CONNECTIONS = 200;
    private final static int CONNECT_TIMEOUT = 30;
    private final static int WRITE_TIMEOUT = 35;


    /**
     *  客戶端配置
     *
     */
    @Bean
    public OkHttpClient okHttpClient() {
        OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
        //讀取超時時間
        clientBuilder.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS);
        //連接超時時間
        clientBuilder.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS);
        //寫入超時時間
        clientBuilder.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS);

        //默認最大5個空閑連接
        clientBuilder.connectionPool(new ConnectionPool(MAX_IDLE_CONNECTIONS, READ_TIMEOUT, TimeUnit.MINUTES));

        return clientBuilder.build();
    }
}

  FeignConfig類上切勿添加@Component注解,一旦添加,它將變成全局配置,這里只用作局部配置。當然,我們在yaml配置文件中已經開啟了OKhttp支持:

# 默認關閉,現開啟
feign.okhttp.enabled=true

由@FeignClient注解的configuration屬性配置這個API接口類的特殊屬性,如果是全局配置,則不需要有configuration指定配置類。eign client代碼如下:

@FeignClient(name = "feignApi", url = "${self.your.url}", configuration = FeignConfig.class)
public interface FeignApiClient {

    @PostMapping(value = "/xxx")
    String query(@RequestBody ParamDTO dto);
  
}

問題分析:連續請求次數比較少時,一切正常,說明配置OKhttp基本配置沒有問題,而連續請求次數非常多(例如600+次)時就出問題,說明TCP連接時間超過了對方TCP長連接有效期,導致拋出異常。

解決辦法把TCP長連接改為短連接,設置headers 屬性 {"Connection=close"}。不論客戶端還是服務端的header,只要包含了值為close的connection,都表明當前正在使用的TCP連接在本次請求處理完畢后會被斷掉,以后客戶端再進行請求時,就必須創建新的TCP連接,而非復用,從而避開TCP長連接有效期的約束。優化后,query函數代碼如下:

@FeignClient(name = "feignApi", url = "${self.your.url}", configuration = FeignConfig.class)
public interface FeignApiClient {
    @PostMapping(value = "/xxx", headers = {"Connection=close"})
    String query(@RequestBody ParamDTO dto);
  
}

  關於TCP長連接和短連接的介紹,請戳《http協議中長連接和短連接介紹》。 老鐵們, 因個人能力有限,難免有瑕疵,如果發現bug或者有更好的建議,那么請在文章下方留言!


免責聲明!

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



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