问题描述: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接口类的特殊属性,feign 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或者有更好的建议,那么请在文章下方留言!