ConnectionPoolTimeoutException: Timeout waiting for connection from pool的错误排查及解决
昨天遇到一个特别奇怪的事情,在系统中本来有个外部接口的方法,运行了好几年,一直正常,突然昨天开始出现异常,并且异常也比较奇怪,排查了好长时间。先看下原始代码:
static {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(5000)
.setConnectionRequestTimeout(2000)
.setSocketTimeout(5000).build();
defaultHttpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig)
.setMaxConnTotal(200)
.setMaxConnPerRoute(100)
.build();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
defaultHttpClient.close();
} catch (IOException ignored) {
}
}));
}
public static String httpGet(String url) {
//先创建一个HttpGet对象,传入目标的网络地址,然后调用HttpClient的execute()方法即可:
HttpGet httpGet = new HttpGet();
httpGet.setURI(URI.create(url));
HttpResponse response = null;
String resp = "";
try {
response = defaultHttpClient.execute(httpGet);
// 取响应的结果
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK) {
resp = EntityUtils.toString(response.getEntity(), "utf-8");
}
} catch (IOException e) {
logger.error("httpGet error:", e);
}
return resp;
}
代码很简单,但是在调用httpGet这个方法的时候,调用一段时间,就出现这个问题。开始以为是并发量太大,但是看流量也没有很大的并发,我们模拟了100个并发,也没有问题,一时不知道怎么排查。然后我们再响应码加了debug日志,生产环境又运行了一段时间,又开始报错,导致客户端接口调用直接失败。我们再仔细观察日志,发现响应码statusCode 有很多500,我们就考虑是不是因为这个的原因呢。
我们查看了这段时间statusCode =500的请求数量,恰好是等于100,而我们出现异常的情况正好是statusCode =500的第100条之后发生的,至此我们明白了,是因为在statusCode =500代码没有做任何处理,导致连接没有释放给连接池。而statusCode == HttpStatus.SC_OK,我们进行了response.getEntity(),这源码中是有关闭连接的方法的。所以我们做了以下修改:
public static String httpGet(String url) {
//先创建一个HttpGet对象,传入目标的网络地址,然后调用HttpClient的execute()方法即可:
HttpGet httpGet = new HttpGet();
httpGet.setURI(URI.create(url));
HttpResponse response = null;
String resp = "";
try {
response = defaultHttpClient.execute(httpGet);
// 取响应的结果
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK) {
resp = EntityUtils.toString(response.getEntity(), "utf-8");
} else {
httpGet.releaseConnection();
}
} catch (Exception e) {
httpGet.releaseConnection();
logger.error("httpGet error:", e);
} finally {
httpGet.abort();
}
return resp;
}
然后模拟了200次的500的请求,系统依然正常,没有再报错。