@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或者有更好的建议,那么请在文章下方留言!

posted @ 2022-01-30 13:57  楼兰胡杨  阅读(4325)  评论(0编辑  收藏  举报