問題描述: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或者有更好的建議,那么請在文章下方留言!