为啥 OkHttp 不重用它的连接?
Posted
技术标签:
【中文标题】为啥 OkHttp 不重用它的连接?【英文标题】:Why OkHttp doesn't reuse its connections?为什么 OkHttp 不重用它的连接? 【发布时间】:2017-04-22 00:46:12 【问题描述】:我正在使用 OkHttp 3.5.0 执行 http 基准测试。我正在向同一个 URL 发送数千个请求。
我希望 OkHttp 客户端使用 ConnectionPool 并一遍又一遍地重用其连接。但是如果我们查看netstat
,我们会看到许多连接处于 TIME_WAIT 状态:
TCP 127.0.0.1:80 127.0.0.1:51752 TIME_WAIT
TCP 127.0.0.1:80 127.0.0.1:51753 TIME_WAIT
TCP 127.0.0.1:80 127.0.0.1:51754 TIME_WAIT
TCP 127.0.0.1:80 127.0.0.1:51755 TIME_WAIT
TCP 127.0.0.1:80 127.0.0.1:51756 TIME_WAIT
...
经过数千次请求后,我收到了SocketException: No buffer space available (maximum connections reached?)
代码执行请求(Kotlin):
val client = OkHttpClient.Builder()
.connectionPool(ConnectionPool(5, 1, TimeUnit.MINUTES))
.build()
val request = Request.Builder().url("http://192.168.0.50").build()
while (true)
val response = client.newCall(request).execute()
response.close()
如果我使用response.body().string()
而不是response.close()
,那么SocketException
不会发生,但netstat
仍然显示大量TIME_WAIT连接,并且基准性能越来越低。
我做错了什么?
PS:我尝试过使用 Apache HttpClient 及其PoolingHttpClientConnectionManager
,看起来效果很好。但我想弄清楚 OkHttp 有什么问题。
【问题讨论】:
你可以试试 response.body().string() 然后是 response.close()。您应该使用响应以便可以重用连接(假设 HTTP/1.1),然后您应该关闭响应。threadCount
是什么?
@YuriSchimke 没有任何改变。其实response.body().string()
消费后执行close()
。
@JesseWilson 从示例代码中删除了threadCount
。它只是一些核心。
@JesseWilson 好像我也有同样的问题。在基准测试期间性能会急剧下降。我不知道 JSnow 是否给你发了测试用例,但无论如何,这是我的:github.com/olegcherr/OkHttp-TimeWait-Test 另外,为了更方便,我创建了一个讨论:github.com/olegcherr/OkHttp-TimeWait-Test/issues/1
【参考方案1】:
我的版本是3.13.0,和3.5.0相差不远,我也遇到TIME_WAIT
的问题。
在深入研究源代码后,我在CallServerInterceptor.java
第 142 行找到:
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection")))
streamAllocation.noNewStreams();
在 StreamAllocation.java 第 367 行:
public void noNewStreams()
Socket socket;
Connection releasedConnection;
synchronized (connectionPool)
releasedConnection = connection;
socket = deallocate(true, false, false); // close connection!
if (connection != null) releasedConnection = null;
closeQuietly(socket);
if (releasedConnection != null)
eventListener.connectionReleased(call, releasedConnection);
这意味着如果请求或响应中存在“Connection: close”标头,okhttp 将CLOSE 连接
虽然问题已经很久没有提交了,但我希望这个答案能帮助遇到这个问题的人,祝你好运。
【讨论】:
【参考方案2】:感谢 toien,我找到了我的解决方案(用于非标准 http 服务器)。
public class Http1CodecWrapper implements HttpCodec
private HttpCodec codec;
public Http1CodecWrapper(HttpCodec codec)
this.codec = codec;
// ...
@Override
public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException
return codec.readResponseHeaders(expectContinue)
.addHeader("Connection", "keep-alive");
OkHttpClient httpClient = new OkHttpClient.Builder()
.addNetworkInterceptor(new Interceptor()
@Override
public Response intercept(Chain chain) throws IOException
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation allocation = realChain.streamAllocation();
HttpCodec codec = new Http1CodecWrapper(realChain.httpStream());
RealConnection connection = (RealConnection) realChain.connection();
return realChain.proceed(request, allocation, codec, connection);
)
.build();
【讨论】:
以上是关于为啥 OkHttp 不重用它的连接?的主要内容,如果未能解决你的问题,请参考以下文章
三、深入理解OkHttp:连接处理-ConnectIntercepter