Quarkus项目使用OkHttp3 Client遇Connection reset问题求助
问题概述
生产环境调用下游API时偶尔抛出如下异常:
java.net.SocketException: Connection reset at java.net.SocketInputStream.read(SocketInputStream.java:186) ~[?:?] at java.net.SocketInputStream.read(SocketInputStream.java:140)
项目作为中间层转发请求:GET请求重试两次仍失败,第三次成功;POST请求因幂等性限制无法重试,首次即失败。
当前核心代码:
public class CustomOkHttpClient extends HttpClient { private OkHttpClient client; public Response callDownstream(HttpRequest clientRequest) { Response clientResponse = null; try { Call call = client.newCall(clientRequest); clientResponse = call.execute(); return parseClientResponse(clientResponse, responseClass); } catch (final Exception exception) { logger.error("Exception occurred"); throw exception; } finally { if (null!=clientResponse) { clientResponse.close(); } } } }
依赖版本:
- okhttp-urlconnection: 5.0.0-alpha.2
- logging-interceptor: 4.9.3
- mockwebserver: 4.9.0
已尝试方案:
- 在finally块中关闭响应连接
- 增加readTimeout,但问题未解决
疑问解答
2. 是否因连接长时间闲置被关闭,导致后续请求失败?
是的,这是该异常最常见的诱因。OkHttp默认通过连接池复用TCP连接,若连接被下游服务器或中间网络设备(负载均衡、防火墙)因闲置超时关闭,OkHttp尝试复用失效连接时就会抛出Connection Reset异常。
GET请求第三次成功,正是因为前两次复用了失效连接,第三次OkHttp会移除失效连接并创建新连接;POST请求未重试,直接使用失效连接导致失败。
另外,你的OkHttp依赖存在跨版本不兼容问题:预览版5.0.0-alpha.2与4.x版本的logging-interceptor、mockwebserver混用,可能导致连接池管理逻辑异常,加重此类问题。
1. 后续可采取的解决方法
(1)统一并升级OkHttp到稳定版
弃用预览版5.0.0-alpha.2,将所有OkHttp相关依赖统一升级到最新稳定版(如4.12.0),避免版本冲突:
<!-- Maven示例 --> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.12.0</version> </dependency> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>logging-interceptor</artifactId> <version>4.12.0</version> </dependency> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>mockwebserver</artifactId> <version>4.12.0</version> </dependency>
(2)调整连接池闲置超时,匹配下游配置
将OkHttp连接池的keepAliveDuration设置为小于下游服务器/网络设备的闲置超时时间(比如下游设300秒,你设250秒),避免复用已被关闭的连接:
OkHttpClient client = new OkHttpClient.Builder() .connectionPool(new ConnectionPool(5, 250, TimeUnit.SECONDS)) .build();
(3)添加连接有效性校验拦截器
自定义拦截器,在请求发送前校验连接是否存活,若失效则强制创建新连接:
public class ConnectionValidityInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Connection connection = chain.connection(); if (connection!= null &&!connection.isAlive()) { connection.socket().close(); // 重置超时参数,强制创建新连接 return chain.withConnectTimeout(0, TimeUnit.MILLISECONDS) .withReadTimeout(0, TimeUnit.MILLISECONDS) .withWriteTimeout(0, TimeUnit.MILLISECONDS) .proceed(request); } return chain.proceed(request); } }
添加到客户端构建器:
OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(new ConnectionValidityInterceptor()) .build();
(4)针对非幂等请求添加定向重试(需谨慎)
若下游POST API支持幂等(比如通过请求ID去重),可针对Connection Reset这类网络异常添加重试逻辑:
public Response callDownstream(HttpRequest clientRequest) { int retryCount = 0; final int maxRetries = 2; while (retryCount <= maxRetries) { Response clientResponse = null; try { Call call = client.newCall(clientRequest); clientResponse = call.execute(); return parseClientResponse(clientResponse, responseClass); } catch (SocketException e) { if (retryCount == maxRetries) { logger.error("Max retries reached for request", e); throw e; } retryCount++; logger.warn("Connection reset, retrying request (attempt " + retryCount + ")"); } catch (final Exception exception) { logger.error("Exception occurred", exception); throw exception; } finally { if (null!= clientResponse) { clientResponse.close(); } } } throw new IllegalStateException("Unexpected retry loop exit"); }
注意:必须确认下游API支持重复调用,否则会导致数据重复提交。
(5)启用详细日志定位问题
通过logging-interceptor开启连接相关日志,生产环境可调整为Level.HEADERS避免日志过大:
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS); OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(loggingInterceptor) .build();
通过日志可确认是否是连接复用导致的异常。
内容的提问来源于stack exchange,提问作者Dinesh Verma

