- Http连接池之:Timeout waiting for con
- timeout lock buildscript cache ,
- Android Studio编译错误:Timeout waiti
- Timeout waiting to lock file has
- Timeout waiting to lock file has
- 引入三方库报错 Error:Timeout waiting t
- 五、AndroidStudio编译时报错:Timeout wai
- HttpClient:Timeout waiting for c
- [sublime]Timeout waiting for det
- Gradle Timeout waiting to lock f
背景
系统 A 调用系统 B 的接口,最近发现当 B 停机维护时,系统 A 在出发到调用系统 B 的接口的功能时会发生阻塞。查看日志,发现异常如下:
org.apache.http.HttpException: Timeout waiting for connection from pool
图1-异常日志
排查
定位到如下代码:
private String executeHttpPost(Request request) throws HttpException {
String responseString = null;
HttpPost httpPost = null;
try {
String countryCode = request.getCustomer().getCountryCode();
String postData = XMLUtil.object2XML(request);
StringBuffer cacheServiceURL = new StringBuffer();
cacheServiceURL.append(serviceURL)
.append("?country=")
.append(countryCode);
System.err.println(cacheServiceURL);
httpPost = new HttpPost(cacheServiceURL.toString());
httpPost.setHeader(HttpHeaders.AUTHORIZATION, "Basic " + userToken);
httpPost.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
HttpEntity requestEntity = new StringEntity(postData, "UTF-8");
httpPost.setEntity(requestEntity);
HttpResponse httpResponse = httpClient.execute(httpPost,localContext);
CacheResponseStatus responseStatus = (CacheResponseStatus) localContext.getAttribute(
CachingHttpClient.CACHE_RESPONSE_STATUS);
System.err.println(responseStatus);
LogContextFactory.singleton().getLogContext().error(this, "The service (" + serviceURL + ") return cache response status " + responseStatus);
int returnHttpStatus = httpResponse.getStatusLine().getStatusCode();
if (HttpStatus.SC_OK == returnHttpStatus) {
if(httpResponse.getEntity() != null)
responseString = EntityUtils.toString(httpResponse.getEntity());
} else {
LogContextFactory.singleton().getLogContext().error(this, "The service (" + serviceURL + ") return status " + returnHttpStatus);
throw new HttpException("The service (" + serviceURL + ") return status " + returnHttpStatus);
}
} catch (Exception e) {
LogContextFactory.singleton().getLogContext().error(this, "When execute executeHttpPost method,a fatal issue has occured.");
LogContextFactory.singleton().getLogContext().error(this, e);
throw new HttpException(e.getMessage());
} finally {
if (httpPost != null) {
httpPost.releaseConnection();
}
}
return responseString;
}
很明显,这段代码负责执行HTTP的POST方法,看起来在finally中也做了connection的关闭。那么分析日志得出结果如下:
- 连接池大小为20
- 在 B 系统维护时,前20次调用都可以立即返回 404
- 后续的调用抛出Timeout waiting for connection from pool 异常
- 在服务器上使用netstat查看TCP状况,发现这些链接出于 ESTABLISHED 状态
通过日志,分析可能是连接池泄露了,于是重新检查这段方法。发现这段代码居然是在try作用域内初始化的,在finally中已经跳出作用域,十分之可疑。
HttpResponse httpResponse = httpClient.execute(httpPost,localContext);
解决
查了一些博客和文档,都说这个 httpResponse 也需要在finally中释放,于是改进后的代码如下:
private String executeHttpPost(Request request) throws HttpException {
String responseString = null;
HttpPost httpPost = null;
HttpResponse httpResponse = null;
try {
//Omit some code
httpResponse = httpClient.execute(httpPost,localContext);
//Omit some code
if (HttpStatus.SC_OK == returnHttpStatus) {
if(httpResponse.getEntity() != null)
responseString = EntityUtils.toString(httpResponse.getEntity());
} else {
//Omit some code
}
} catch (Exception e) {
//Omit some code
} finally {
if (httpResponse != null) {
try {
EntityUtils.consume(httpPost.getEntity());
} catch (IOException e) {
e.printStackTrace();
}
}
if (httpPost != null) {
httpPost.releaseConnection();
}
}
return responseString;
}
运行结果:
![](https://img.haomeiwen.com/i1863312/8ea0f8ff80c13b02.png)
当调用的服务不可用时,确实立刻反回了404,不会再阻塞业务。
Solution分析
疑惑
- 为什么 B 服务正常返回的时候不会出现连接池泄露?
- finally中调用的 httpPost.releaseConnection() 是干嘛的?
首先看代码的这部分代码:
![](https://img.haomeiwen.com/i1863312/624221f93d77334d.png)
这部分代码并没有处理返回状态不是 200 的情况,所以直接看 finally 块:
if (httpPost != null) {
httpPost.releaseConnection();
}
这段代码看起来是在释放什么东西,点进源码发现其实就是对连接进行重置而已:
![](https://img.haomeiwen.com/i1863312/a997a23a0ed2230f.png)
![](https://img.haomeiwen.com/i1863312/56097e1549ec9d37.png)
原来这个releaseConnection方法仅仅是对连接的状态进行标记,使其可以标记为被复用的连接。那么为什么返回状态码是 200 的正常情况不出现连接池泄露呢?返回图3,当返回状态码为200时,只有这一行代码:
responseString = EntityUtils.toString(httpResponse.getEntity());
查看发现 EntityUtils.toString() 方法调用了关闭流的方法!
![](https://img.haomeiwen.com/i1863312/687e7abf46f8d108.png)
![](https://img.haomeiwen.com/i1863312/4ae0bac8475c3635.png)
再来看后来在finally块中添加上的这段代码:
EntityUtils.consume(httpPost.getEntity());
![](https://img.haomeiwen.com/i1863312/faa57843a6e70955.png)
一目了然,这才是真正关闭了流。
总结
但凡遇到什么“池”,都要再三小心,尤其在异常情况下释放资源这块。
网友评论