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
相关产品推荐
相关产品推荐

