HttpClient 异常:org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
1. 背景
前几天网络切换,完事之后系统监控发现,某后台系统通过http调用某接口,出现很多org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
(等待池中的连接超时)异常;
2. 分析
httpClient版本:4.5.2
看了下HttpClient的池配置、超时时间,感觉没啥问题,并且也开启了监控线程,用来对异常和空闲连接进行释放,看下出问题的代码。
1 | public Tuple2<Integer, String> queryEvaluationData(String commentGeneralGoodsCode, String companyCode) { |
以上代码,重点是这一段:当状态码为200,进入该分支,调用 EntityUtils.toByteArray()
方法;
toByteArray()
方法的 finally
中有 close()
方法,会关闭资源连接,这个没有问题。
1 | if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { |
上面逻辑代码中有一个问题:只有状态为 200 的 response,才会被处理并释放连接。
状态非200的连接,不会被释放连接。
大致思路来了:网络切换时,无状态、状态非200的请求,占用了部分连接池(具体TCP就不贴了)。
但又有一个困惑,为啥监控线程没有起到释放异常和空闲连接的作用?这个问题,后面加点监控持续观察吧。
3. 优化
优化后,在代码最后增加一个finally,用来兜底归还连接。
EntityUtils.consumeQuietly()
方法,确保 response.Entity
被消耗,资源释放。
1 | public Tuple2<Integer, String> queryEvaluationData(String commentGeneralGoodsCode, String companyCode) { |
4. 后续
代码评审时,有同事提出异议。为什么不用 response.close()
方法关闭连接?
response.close()
方法并不是归还连接到连接池,而是关闭连接。
使用 EntityUtils
中的方法:consumeQuietly(HttpEntity)
、consume(HttpEntity)
或者自己从 response.getEntity().getContent()
获取流,读取完毕会自动归还连接,或者不读取主动调用流的 close()
来归还连接到连接池。
在HttpClient - Fundamentals 1.1.5中有详细解释:
The difference between closing the content stream and closing the response is that the former will attempt to keep the underlying connection alive by consuming the entity content while the latter immediately shuts down and discards the connection.
关闭内容流和关闭响应的区别在于,前者会试图通过消耗实体内容来保持底层连接的活力,而后者则会立即关闭并丢弃连接;
5. 其他优化
- httpclient 是线程安全的,没必要每个线程使用时都创建一个。
- 所有entity,最后都要确保被消耗、释放。
- 使用 PoolingHttpClientConnectionManager 作为连接池(HttpClient4)
- 设置 closeExpiredConnections 、closeIdleConnections(后台监控线程),关闭 异常、空闲连接。
- 合理的超时设置(ConnectTimeout、SocketTimeout、ConnectionRequestTimeout等)
6. Reference
- HttpClient - Fundamentals
- What’s the difference between CloseableHttpResponse.close() and httpPost.releaseConnection()