You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apache HttpClient读取超时后连接关闭延迟问题咨询

问题描述

我使用Java 11和Spring Boot开发应用,通过RestTemplate调用两个外部REST API,Apache HttpClient配置如下:

@Bean
public RestTemplate proxyRestTemplate() {
  HttpHost proxy = new HttpHost(HTTP_PROXY_HOST, HTTP_PROXY_PORT);
  RequestConfig requestConfig =
      RequestConfig.custom()
          .setProxy(proxy)
          .setCircularRedirectsAllowed(false)
          .setRedirectsEnabled(false)
          .setConnectionRequestTimeout(2000)
          .setConnectTimeout(2000)
          .setSocketTimeout(8000)
          .build();
  PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
  connectionManager.setMaxTotal(40);
  connectionManager.setDefaultMaxPerRoute(20);
  connectionManager.closeExpiredConnections();

  CloseableHttpClient httpClient =
      HttpClientBuilder.create()
          .setConnectionManager(connectionManager)
          .setDefaultRequestConfig(requestConfig)
          .setConnectionManagerShared(true)
          .disableAutomaticRetries()
          .setConnectionTimeToLive(1, TimeUnit.MINUTES)
          .build();

  HttpComponentsClientHttpRequestFactory requestFactory =
      new HttpComponentsClientHttpRequestFactory(httpClient);
  return new RestTemplate(requestFactory);
}

基于该配置,我通过Spring Integration的Http.outboundGateway()调用API,代码如下:

public IntegrationFlow getDecision() {
  return flow -> {
    flow.channel(c -> c.executor(getAsyncExecutor()))
        .handle(
            Http.outboundGateway(myURL, restTemplateConfig.proxyRestTemplate())
                .mappedRequestHeaders("Authorization", "Content-Type")
                .extractPayload(true)
                .httpMethod(HttpMethod.POST)
                .expectedResponseType(String.class));
  };
}

读取超时(已设置为8秒)发生后,连接需要额外7-8秒才能关闭并释放回连接池,导致应用总响应时长增至15-16秒。相关日志记录如下:

DEBUG 2023-09-03T21:00:50.646440127Z [resource.labels.containerName: my-service] http-outgoing-9 >> "{"request":{"Demo request":""}
DEBUG 2023-09-03T21:00:58.651312241Z [resource.labels.containerName: my-service] http-outgoing-9 << "[read] I/O error: Read timed out"
DEBUG 2023-09-03T21:00:58.651390941Z [resource.labels.containerName: my-service] http-outgoing-9: Close connection
DEBUG 2023-09-03T21:01:06.657634687Z [resource.labels.containerName: my-service] http-outgoing-9: Shutdown connection
DEBUG 2023-09-03T21:01:06.657706848Z [resource.labels.containerName: my-service] Connection discarded
DEBUG 2023-09-03T21:01:06.657729276Z [resource.labels.containerName: my-service] Connection released: [id: 9][route: {tls}->http://ep.threatpulse.net:80->https://api.xyz.com:443][total available: 3; route allocated: 3 of 20; total allocated: 4 of 40]
问题原因及解决办法

从日志时间线可以看到,读取超时在21:00:58触发,但连接直到21:01:06才完成关闭释放,间隔约8秒。这是因为Apache HttpClient遇到SocketTimeoutException后,默认会尝试优雅关闭连接,如果远端服务器未响应FIN包,就会等待TCP默认超时(通常为2倍MSL,约8秒)后才强制关闭连接。

针对该问题,可通过以下配置优化:

  • 强制关闭异常连接,跳过优雅关闭流程
    修改HttpClientBuilder配置,自定义连接复用策略,当请求出现I/O异常时直接标记连接不可复用,立即关闭:
CloseableHttpClient httpClient =
    HttpClientBuilder.create()
        .setConnectionManager(connectionManager)
        .setDefaultRequestConfig(requestConfig)
        .setConnectionManagerShared(true)
        .disableAutomaticRetries()
        .setConnectionTimeToLive(1, TimeUnit.MINUTES)
        // 自定义连接复用策略,异常连接直接关闭
        .setConnectionReuseStrategy(new DefaultConnectionReuseStrategy() {
            @Override
            public boolean keepAlive(HttpResponse response, HttpContext context) {
                IOException ioException = (IOException) context.getAttribute(HttpCoreContext.HTTP_RESPONSE_CONNECTION);
                if (ioException != null) {
                    return false;
                }
                return super.keepAlive(response, context);
            }
        })
        .build();
  • 定期主动清理连接池
    当前仅调用closeExpiredConnections(),可添加定时任务定期清理空闲和过期连接,避免连接长时间占用:
// 需在启动类添加@EnableScheduling注解开启定时任务
@Scheduled(fixedRate = 30000)
public void cleanConnectionPool() {
    connectionManager.closeExpiredConnections();
    // 关闭30秒内未使用的空闲连接
    connectionManager.closeIdleConnections(30, TimeUnit.SECONDS);
}
  • 双重保障读取超时配置
    在HttpComponentsClientHttpRequestFactory上显式设置读取超时,与RequestConfig的SocketTimeout形成双重保障:
HttpComponentsClientHttpRequestFactory requestFactory =
    new HttpComponentsClientHttpRequestFactory(httpClient);
requestFactory.setReadTimeout(8000); // 与SocketTimeout保持一致

另外,注意setConnectionManagerShared(true)表示连接管理器被多个HttpClient实例共享,需确保所有使用该管理器的客户端都遵循相同的连接策略,避免出现配置冲突。

内容的提问来源于stack exchange,提问作者Somnath Mukherjee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 23:09:51