HttpClient连接池抛出大量ConnectionPoolTimeoutException: Timeout waiting for connection异常排查 以你之姓@ 2022-06-04 00:36 103阅读 0赞 今天解决了一个HttpClient的异常,汗啊,一个HttpClient使用稍有不慎都会是毁灭级别的啊。 这里有之前因为route配置不当导致服务器异常的一个处理:[http://blog.csdn.net/shootyou/article/details/6415248][http_blog.csdn.net_shootyou_article_details_6415248] 里面的HttpConnectionManager实现就是我在这里使用的实现。 问题表现: tomcat后台日志发现大量异常 \[plain\] [view plain][] [copy][view plain] 1. org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection 时间一长 tomcat就无法继续处理其他请求,从假死变成真死了 。 linux运行: \[plain\] [view plain][] [copy][view plain] 1. netstat -n | awk '/^tcp/ \{++S\[$NF\]\} END \{for(a in S) print a, S\[a\]\}' 发现CLOSE\_WAIT的数量始终在400以上,一直没降过。 问题分析: 一开始我对我的HttpClient使用过程深信不疑,我不认为异常是来自这里。 所以我开始从TCP的连接状态入手,猜测可能导致异常的原因。以前经常遇到TIME\_WAIT数过大导致的服务器异常,很容易解决,修改下sysctl就ok了。但是这次是CLOSE\_WAIT,是完全不同的概念了。 关于TIME\_WAIT和CLOSE\_WAIT的区别和异常处理我会单独起一篇文章详细说说我的理解。 简单来说CLOSE\_WAIT数目过大是由于被动关闭连接处理不当导致的。 我说一个场景,服务器A会去请求服务器B上面的apache获取文件资源,正常情况下,如果请求成功,那么在抓取完资源后服务器A会主动发出关闭连接的请求,这个时候就是主动关闭连接,连接状态我们可以看到是TIME\_WAIT。如果一旦发生异常呢?假设请求的资源服务器B上并不存在,那么这个时候就会由服务器B发出关闭连接的请求,服务器A就是被动的关闭了连接,如果服务器A被动关闭连接之后自己并没有释放连接,那就会造成CLOSE\_WAIT的状态了。 所以很明显,问题还是处在程序里头。 先看看我的HttpConnectionManager实现: \[java\] [view plain][] [copy][view plain] 1. **public** **class** HttpConnectionManager \{ 2. 3. **private** **static** HttpParams httpParams; 4. **private** **static** ClientConnectionManager connectionManager; 5. 6. /\*\* 7. \* 最大连接数 8. \*/ 9. **public** **final** **static** **int** MAX\_TOTAL\_CONNECTIONS = 800; 10. /\*\* 11. \* 获取连接的最大等待时间 12. \*/ 13. **public** **final** **static** **int** WAIT\_TIMEOUT = 60000; 14. /\*\* 15. \* 每个路由最大连接数 16. \*/ 17. **public** **final** **static** **int** MAX\_ROUTE\_CONNECTIONS = 400; 18. /\*\* 19. \* 连接超时时间 20. \*/ 21. **public** **final** **static** **int** CONNECT\_TIMEOUT = 10000; 22. /\*\* 23. \* 读取超时时间 24. \*/ 25. **public** **final** **static** **int** READ\_TIMEOUT = 10000; 26. 27. **static** \{ 28. httpParams = **new** BasicHttpParams(); 29. // 设置最大连接数 30. ConnManagerParams.setMaxTotalConnections(httpParams, MAX\_TOTAL\_CONNECTIONS); 31. // 设置获取连接的最大等待时间 32. ConnManagerParams.setTimeout(httpParams, WAIT\_TIMEOUT); 33. // 设置每个路由最大连接数 34. ConnPerRouteBean connPerRoute = **new** ConnPerRouteBean(MAX\_ROUTE\_CONNECTIONS); 35. ConnManagerParams.setMaxConnectionsPerRoute(httpParams,connPerRoute); 36. // 设置连接超时时间 37. HttpConnectionParams.setConnectionTimeout(httpParams, CONNECT\_TIMEOUT); 38. // 设置读取超时时间 39. HttpConnectionParams.setSoTimeout(httpParams, READ\_TIMEOUT); 40. 41. SchemeRegistry registry = **new** SchemeRegistry(); 42. registry.register(**new** Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); 43. registry.register(**new** Scheme("https", SSLSocketFactory.getSocketFactory(), 443)); 44. 45. connectionManager = **new** ThreadSafeClientConnManager(httpParams, registry); 46. \} 47. 48. **public** **static** HttpClient getHttpClient() \{ 49. **return** **new** DefaultHttpClient(connectionManager, httpParams); 50. \} 51. 52. \} 看到没MAX\_ROUTE\_CONNECTIONS 正好是400,跟CLOSE\_WAIT非常接近啊,难道是巧合?继续往下看。 然后看看调用它的代码是什么样的: \[java\] [view plain][] [copy][view plain] 1. **public** **static** String readNet (String urlPath) 2. \{ 3. StringBuffer sb = **new** StringBuffer (); 4. HttpClient client = **null**; 5. InputStream in = **null**; 6. InputStreamReader isr = **null**; 7. **try** 8. \{ 9. client = HttpConnectionManager.getHttpClient(); 10. HttpGet get = **new** HttpGet(); 11. get.setURI(**new** URI(urlPath)); 12. HttpResponse response = client.execute(get); 13. **if** (response.getStatusLine ().getStatusCode () != 200) \{ 14. **return** **null**; 15. \} 16. HttpEntity entity =response.getEntity(); 17. 18. **if**( entity != **null** )\{ 19. in = entity.getContent(); 20. ..... 21. \} 22. **return** sb.toString (); 23. 24. \} 25. **catch** (Exception e) 26. \{ 27. e.printStackTrace (); 28. **return** **null**; 29. \} 30. **finally** 31. \{ 32. **if** (isr != **null**)\{ 33. **try** 34. \{ 35. isr.close (); 36. \} 37. **catch** (IOException e) 38. \{ 39. e.printStackTrace (); 40. \} 41. \} 42. **if** (in != **null**)\{ 43. **try** 44. \{ 45. <span style="color:\#ff0000;">in.close ();</span> 46. \} 47. **catch** (IOException e) 48. \{ 49. e.printStackTrace (); 50. \} 51. \} 52. \} 53. \} 很简单,就是个远程读取中文页面的方法。值得注意的是这一段代码是后来某某同学加上去的,看上去没啥问题,是用于非200状态的异常处理: \[java\] [view plain][] [copy][view plain] 1. **if** (response.getStatusLine ().getStatusCode () != 200) \{ 2. **return** **null**; 3. \} 代码本身没有问题,但是问题是放错了位置。如果这么写的话就没问题: \[java\] [view plain][] [copy][view plain] 1. client = HttpConnectionManager.getHttpClient(); 2. HttpGet get = **new** HttpGet(); 3. get.setURI(**new** URI(urlPath)); 4. HttpResponse response = client.execute(get); 5. 6. HttpEntity entity =response.getEntity(); 7. 8. **if**( entity != **null** )\{ 9. in = entity.getContent(); 10. .......... 11. \} 12. 13. **if** (response.getStatusLine ().getStatusCode () != 200) \{ 14. **return** **null**; 15. \} 16. **return** sb.toString (); 看出毛病了吧。在这篇入门( [HttpClient4.X 升级 入门 + http连接池使用][http_blog.csdn.net_shootyou_article_details_6415248] )里头我提到了HttpClient4使用我们常用的InputStream.close()来确认连接关闭,前面那种写法InputStream in 根本就不会被赋值,意味着一旦出现非200的连接,这个连接将永远僵死在连接池里头,太恐怖了。。。所以我们看到CLOST\_WAIT数目为400,因为对一个路由的连接已经完全被僵死连接占满了。。。 其实上面那段代码还有一个没处理好的地方,异常处理不够严谨,所以最后我把代码改成了这样: \[java\] [view plain][] [copy][view plain] 1. **public** **static** String readNet (String urlPath) 2. \{ 3. StringBuffer sb = **new** StringBuffer (); 4. HttpClient client = **null**; 5. InputStream in = **null**; 6. InputStreamReader isr = **null**; 7. HttpGet get = **new** HttpGet(); 8. **try** 9. \{ 10. client = HttpConnectionManager.getHttpClient(); 11. get.setURI(**new** URI(urlPath)); 12. HttpResponse response = client.execute(get); 13. **if** (response.getStatusLine ().getStatusCode () != 200) \{ 14. get.abort(); 15. **return** **null**; 16. \} 17. HttpEntity entity =response.getEntity(); 18. 19. **if**( entity != **null** )\{ 20. in = entity.getContent(); 21. ...... 22. \} 23. **return** sb.toString (); 24. 25. \} 26. **catch** (Exception e) 27. \{ 28. get.abort(); 29. e.printStackTrace (); 30. **return** **null**; 31. \} 32. **finally** 33. \{ 34. **if** (isr != **null**)\{ 35. **try** 36. \{ 37. isr.close (); 38. \} 39. **catch** (IOException e) 40. \{ 41. e.printStackTrace (); 42. \} 43. \} 44. **if** (in != **null**)\{ 45. **try** 46. \{ 47. in.close (); 48. \} 49. **catch** (IOException e) 50. \{ 51. e.printStackTrace (); 52. \} 53. \} 54. \} 55. \} 显示调用HttpGet的abort,这样就会直接中止这次连接,我们在遇到异常的时候应该显示调用,因为谁能保证异常是在InputStream in赋值之后才抛出的呢。 文章来源:[http://blog.csdn.net/shootyou/article/details/6615051][http_blog.csdn.net_shootyou_article_details_6615051] [http_blog.csdn.net_shootyou_article_details_6415248]: http://blog.csdn.net/shootyou/article/details/6415248 [view plain]: http://blog.csdn.net/shootyou/article/details/6615051# [http_blog.csdn.net_shootyou_article_details_6615051]: http://blog.csdn.net/shootyou/article/details/6615051
还没有评论,来说两句吧...