【问题标题】:Connection closed when sleeping long durations in between HttpClient requests在 HttpClient 请求之间长时间休眠时连接关闭
【发布时间】:2015-04-18 05:44:56
【问题描述】:

我第一次使用 Apache HttpComponents,特别是 HttpClient。我有一个相当基本的用例。我每 N 秒轮询一次第三方服务器(在这种情况下,他们的 API 需要 POST)。

CloseableHttpClient httpclient = HttpClients.custom().
    setDefaultCookieStore(cookieStore).build();

然后我在循环中执行一些 POST 请求...使用:

while (!someCondition) {
    HttpPost httpPost = ...
    httpclient.execute(httpPost)
    Thread.sleep(SOME_TIME)
}

我注意到如果我睡了更长的时间,比如 3 分钟,那么我就没有从服务器得到响应,并且每次连接都会中断:

DEBUG [org.apache.http.wire] http-outgoing-1 << "end of stream"
DEBUG [org.apache.http.impl.conn.DefaultManagedHttpClientConnection] http-outgoing-1: Close connection
DEBUG [org.apache.http.impl.conn.DefaultManagedHttpClientConnection] http-outgoing-1: Shutdown connection
DEBUG [org.apache.http.impl.execchain.MainClientExec] Connection discarded
DEBUG [org.apache.http.impl.conn.DefaultManagedHttpClientConnection] http-outgoing-1: Close connection

我不相信它是服务器。我可能没有正确使用 HttpComponents,或者我的配置错误。如果我将其设置为较短的持续时间(例如 1 分钟),它可以正常工作(我确实注意到它在运行约 15 分钟后死亡 - 所以这是 15 个一分钟的间隔)。

为了发送请求,我将它包装在一些 Java 8 lambda 中并使用资源尝试,但我认为这并不重要:

private <R> R sendRequest(HttpUriRequest request, Function<String, R> func) {
    try {
        try (CloseableHttpResponse resp = this.httpclient.execute(request)) {
            HttpEntity entity = resp.getEntity();

            String responseString = EntityUtils.toString(entity);

            R result = null;
            if (func != null) {
                result = func.apply(responseString);
            }

            EntityUtils.consume(entity);
            return result;
        }
    } catch (IOException e) {
        e.printStackTrace();
        throw new RuntimeException("Error sending request: " + request, e);
    }
}

实际的例外是:

Caused by: org.apache.http.NoHttpResponseException: myserver.com:80 failed to respond
    at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:143)
    at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:57)
    at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:261)
    at org.apache.http.impl.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:165)
    at org.apache.http.impl.conn.CPoolProxy.receiveResponseHeader(CPoolProxy.java:167)
    at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:272)
    at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:124)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:271)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)

【问题讨论】:

  • 您显示的最后一个异常真的是异常的根本原因吗? Java的网络层什么都没有(IOException?)
  • 我相信是的。它目前已经运行了大约 25 分钟(大约 30 秒的睡眠时间),所以我现在不想杀死它。但我会跟进。
  • 你的客户端和http服务器之间有防火墙吗?
  • 如果您在日志中看到 'http-outgoing-1
  • @oleg 我明白这似乎正在发生。我不明白为什么如果我创建一个 HttpClient 实例,发送一个请求,等待 3 分钟,然后发送另一个请求,每次都会失败。但如果我改为睡 30 秒左右,它可以正常运行近 2 小时。每次我想发送请求时,我是否应该创建一个新的 HttpClient 实例?我假设我可以拥有一个实例,它为我管理池连接、处理重试等(因为我使用的是那些默认配置)。

标签: java apache-httpclient-4.x apache-httpcomponents


【解决方案1】:

为了节省资源,HTTP 服务器关闭在最长不活动期间内一直处于空闲状态的持久连接是非常常见和自然的。在您的特定情况下,如果客户端每 30 秒生成一个请求,则服务器保持连接处于活动状态,而如果连接保持空闲更长时间,它最终会在服务器端关闭(并在客户端变为半关闭或“陈旧”) .下次客户端从池中租用连接并尝试使用它来执行请求时,执行失败并出现 I/O 异常。这是完全正常的,可以预料到。 HttpClient 尝试通过执行所谓的陈旧连接检查来缓解该问题,以查明连接是否已被对方端点关闭或是否已变为无效。 “陈旧”支票相对昂贵,应谨慎使用。从 4.4 版开始的 HttpClient 旨在通过有选择地执行检查来提高开箱即用的性能。 4.4 版中有一个缺陷,基本上完全禁用了连接验证。请升级到 4.4.1 版本,问题应该会消失。

说了这么多,我仍然认为在你的情况下最好的做法是在长时间不活动之前使用HttpClientConnectionManager#closeIdleConnections从连接池中逐出所有持久连接

【讨论】:

  • 谢谢!在我的特殊情况下,我不知道关闭 HttpClient、让它获得 GC 并创建一个新实例是否有任何特别的缺点。保持相同的 HttpClient 实例带来的任何性能提升都可以忽略不计,因为我在请求之间的几分钟内睡眠。除了性能之外还有其他原因可以保留单个 HttpClient 实例吗?再次感谢。
  • 保持对话状态:cookies、授权缓存、用户令牌等
【解决方案2】:

我遇到了类似的问题(NoHttpResponseException: myserver:port failed to respond),所有 GET 请求都正常工作,所有 POST 和 PUT 请求都失败。这是由于 HTTP 客户端默认压缩了请求正文,而服务器不期望它压缩并将请求视为空正文。

这些是服务器端的日志:

[2017-03-01 11:32:51,074][WARN ][http.netty][host-elasticsearch] Caught exception while handling client http traffic, closing connection [details]
TransportException[Support for compressed content is disabled. You can enable it with http.compression=true]

[2017-03-01 11:32:51,074][WARN ][http.netty] [host-elasticsearch] Caught exception while handling client http traffic, closing connection [details]
java.lang.IllegalStateException: received HttpChunk without HttpMessage

我使用 jerseyClient (Dropwizard) 解决了这个问题,只需在 yaml 配置中设置:

jersey-client:
  gzipEnabled: false

希望这对某人有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-03
    相关资源
    最近更新 更多