转自: http://blog.csdn.net/shootyou/article/details/6615051

今天解决了一个HttpClient的异常,汗啊,一个HttpClient使用稍有不慎都会是毁灭级别的啊。

这里有之前因为route配置不当导致服务器异常的一个处理:http://blog.csdn.net/shootyou/article/details/6415248

里面的HttpConnectionManager实现就是我在这里使用的实现。

问题表现:

tomcat后台日志发现大量异常

  1. org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection

时间一长tomcat就无法继续处理其他请求,从假死变成真死了。

linux运行:

  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实现:

  1. public class HttpConnectionManager {
  2. private static HttpParams httpParams;
  3. private static ClientConnectionManager connectionManager;
  4. /**
  5. * 最大连接数
  6. */
  7. public final static int MAX_TOTAL_CONNECTIONS = 800;
  8. /**
  9. * 获取连接的最大等待时间
  10. */
  11. public final static int WAIT_TIMEOUT = 60000;
  12. /**
  13. * 每个路由最大连接数
  14. */
  15. public final static int MAX_ROUTE_CONNECTIONS = 400;
  16. /**
  17. * 连接超时时间
  18. */
  19. public final static int CONNECT_TIMEOUT = 10000;
  20. /**
  21. * 读取超时时间
  22. */
  23. public final static int READ_TIMEOUT = 10000;
  24. static {
  25. httpParams = new BasicHttpParams();
  26. // 设置最大连接数
  27. ConnManagerParams.setMaxTotalConnections(httpParams, MAX_TOTAL_CONNECTIONS);
  28. // 设置获取连接的最大等待时间
  29. ConnManagerParams.setTimeout(httpParams, WAIT_TIMEOUT);
  30. // 设置每个路由最大连接数
  31. ConnPerRouteBean connPerRoute = new ConnPerRouteBean(MAX_ROUTE_CONNECTIONS);
  32. ConnManagerParams.setMaxConnectionsPerRoute(httpParams,connPerRoute);
  33. // 设置连接超时时间
  34. HttpConnectionParams.setConnectionTimeout(httpParams, CONNECT_TIMEOUT);
  35. // 设置读取超时时间
  36. HttpConnectionParams.setSoTimeout(httpParams, READ_TIMEOUT);
  37. SchemeRegistry registry = new SchemeRegistry();
  38. registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
  39. registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
  40. connectionManager = new ThreadSafeClientConnManager(httpParams, registry);
  41. }
  42. public static HttpClient getHttpClient() {
  43. return new DefaultHttpClient(connectionManager, httpParams);
  44. }
  45. }

看到没MAX_ROUTE_CONNECTIONS 正好是400,跟CLOSE_WAIT非常接近啊,难道是巧合?继续往下看。

然后看看调用它的代码是什么样的:

  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. if( entity != null ){
  18. in = entity.getContent();
  19. .....
  20. }
  21. return sb.toString ();
  22. }
  23. catch (Exception e)
  24. {
  25. e.printStackTrace ();
  26. return null;
  27. }
  28. finally
  29. {
  30. if (isr != null){
  31. try
  32. {
  33. isr.close ();
  34. }
  35. catch (IOException e)
  36. {
  37. e.printStackTrace ();
  38. }
  39. }
  40. if (in != null){
  41. try
  42. {
  43. <span style="color:#ff0000;">in.close ();</span>
  44. }
  45. catch (IOException e)
  46. {
  47. e.printStackTrace ();
  48. }
  49. }
  50. }
  51. }

很简单,就是个远程读取中文页面的方法。值得注意的是这一段代码是后来某某同学加上去的,看上去没啥问题,是用于非200状态的异常处理:

  1. if (response.getStatusLine ().getStatusCode () != 200) {
  2. return null;
  3. }

代码本身没有问题,但是问题是放错了位置。如果这么写的话就没问题:

  1. client = HttpConnectionManager.getHttpClient();
  2. HttpGet get = new HttpGet();
  3. get.setURI(new URI(urlPath));
  4. HttpResponse response = client.execute(get);
  5. HttpEntity entity =response.getEntity();
  6. if( entity != null ){
  7. in = entity.getContent();
  8. ..........
  9. }
  10. if (response.getStatusLine ().getStatusCode () != 200) {
  11. return null;
  12. }
  13. return sb.toString ();

看出毛病了吧。在这篇入门(HttpClient4.X 升级 入门 + http连接池使用)里头我提到了HttpClient4使用我们常用的InputStream.close()来确认连接关闭,前面那种写法InputStream in 根本就不会被赋值,意味着一旦出现非200的连接,这个连接将永远僵死在连接池里头,太恐怖了。。。所以我们看到CLOST_WAIT数目为400,因为对一个路由的连接已经完全被僵死连接占满了。。。

其实上面那段代码还有一个没处理好的地方,异常处理不够严谨,所以最后我把代码改成了这样:

  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. if( entity != null ){
  19. in = entity.getContent();
  20. ......
  21. }
  22. return sb.toString ();
  23. }
  24. catch (Exception e)
  25. {
  26. get.abort();
  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. in.close ();
  46. }
  47. catch (IOException e)
  48. {
  49. e.printStackTrace ();
  50. }
  51. }
  52. }
  53. }

显示调用HttpGet的abort,这样就会直接中止这次连接,我们在遇到异常的时候应该显示调用,因为谁能保证异常是在InputStream in赋值之后才抛出的呢。

好了 ,分析完毕,明天准备总结下CLOSE_WAIT和TIME_WAIT的区别。

HttpClient连接池抛出大量ConnectionPoolTimeoutException: Timeout waiting for connection异常排查的更多相关文章

  1. HttpClient Timeout waiting for connection from pool 问题解决方案

    错误:org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool 前言 ...

  2. springboot使用RestTemplate+httpclient连接池发送http消息

    简介 RestTemplate是spring支持的一个请求http rest服务的模板对象,性质上有点像jdbcTemplate RestTemplate底层还是使用的httpclient(org.a ...

  3. Http持久连接与HttpClient连接池

    一.背景 HTTP协议是无状态的协议,即每一次请求都是互相独立的.因此它的最初实现是,每一个http请求都会打开一个tcp socket连接,当交互完毕后会关闭这个连接. HTTP协议是全双工的协议, ...

  4. Http 持久连接与 HttpClient 连接池

    一.背景 HTTP协议是无状态的协议,即每一次请求都是互相独立的.因此它的最初实现是,每一个http请求都会打开一个tcp socket连接,当交互完毕后会关闭这个连接. HTTP协议是全双工的协议, ...

  5. HttpClient 的Timeout waiting for connection from pool

    Timeout waiting for connection from pool 异常 httpClient大家用到地方会很多,先简单描述一下几个关键配置的意义 httpClient版本为4.5.1 ...

  6. HttpClient连接池的一些思考

    前言 使用apache的httpclient进行http的交互处理已经很长时间了,而httpclient实例则使用了http连接池,想必大家也没有关心过连接池的管理.事实上,通过分析httpclien ...

  7. HttpClient连接池

    HttpClient连接池,发现对于高并发的请求,效率提升很大.虽然知道是因为建立了长连接,导致请求效率提升,但是对于内部的原理还是不太清楚.后来在网上看到了HTTP协议的发展史,里面提到了一个属性C ...

  8. HttpClient实战三:Spring整合HttpClient连接池

    简介 在微服务架构或者REST API项目中,使用Spring管理Bean是很常见的,在项目中HttpClient使用的一种最常见方式就是:使用Spring容器XML配置方式代替Java编码方式进行H ...

  9. httpclient连接池在ES Restful API请求中的应用

    package com.wm.utils; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http ...

随机推荐

  1. 数据结构之顺序表,c#实现

    using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using S ...

  2. Palindrome Linked List 解答

    Question Given a singly linked list, determine if it is a palindrome. Follow up:Could you do it in O ...

  3. Binary Search Tree DFS Template

    Two methods: 1. Traverse 2. Divide & Conquer // Traverse: usually do not have return value publi ...

  4. Hdu3436-Queue-jumpers(伸展树)

    Description Ponyo and Garfield are waiting outside the box-office for their favorite movie. Because ...

  5. 【Python脚本】Eclipse IDE扩展PyDev插件安装

    作为一名Python的初学者,其实不用太在意IDE了,我觉得开始的时候用用自带的 IDLE 也挺好的. 还有 DreamPie 也挺好的.都是一些轻量级的IDE. 因为我正好安装有Eclipse,平时 ...

  6. 《数据通信与网络》笔记--IPSec

    1.IP层的安全:IPSec IP层安全(IP security,IPsec)是由因特网工程任务组(IETF)设计用来为IP层的分组提供安全的一组协议.IPsec帮助 生成经过鉴别的与安全的IP层的分 ...

  7. 【转】Linux awk命令详解

    简介 awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大.简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再 ...

  8. Linux文件权限管理

    一.设置文件所属的用户以及所属的组(chown,chgrp) chgrp用来更改文件的组拥有者,其一般格式为:chgrp [option] group file(1)把文件test的组拥有者改为zfs ...

  9. Qt之操作Excel

    Visual Basic for Applications(VBA)是一种Visual Basic的一种宏语言,主要能用来扩展Windows的应用程式功能,特别是Microsoft Office软件. ...

  10. 22. 使用 awk / grep / head / tail 命令进行文本 / 日志分析 (/home/D/acc.log)

    一.awk     # 统计 a-read-file 接口 中,接口耗时 超过 0.007 秒的有多少个请求     D@Demon ~]$ awk '$7>0.007' acc.log | w ...