转自: 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. 2015第15周六Java线程池

    Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具.真正的线程池接口是ExecutorService. 比较重要的几个类: Ex ...

  2. bzoj1734 [Usaco2005 feb]Aggressive cows 愤怒的牛

    Description Farmer John has built a new long barn, with N (2 <= N <= 100,000) stalls. The stal ...

  3. Node.js 和Socket.IO 实现chat WEBIM

    socket官方:   http://socket.io/  需求:实现WEB IM功能,数据从服务器PUSH  不是PULL  websocket是基于HTML5的新特性,不兼容IE6,7,8 .. ...

  4. MCM1988 问题B_lingo_装货问题

    两辆平板车的装货问题有七种规格的包装箱要装到两辆铁路平板车上去包装箱的宽和高是一样的但厚度(t,以厘米计)及重量(,以公斤计)是不同的.下表给出了每种包装箱的厚度重量以及数量每辆平板车有10.2 米长 ...

  5. 类linux 系统iptables 系统初始化配置

    #!/bin/bash iptables -F iptables -X /etc/rc.d/init.d/iptables save service iptables restart iptables ...

  6. python高级编程(第12章:优化学习)2

    #优化策略 #3个原则 """ 1a:寻找其他原因:确定第三方服务器或资源不是问题所在 2a:度量硬件:确定资源足够用 3a:编写速度测试:创建带有速度要求的场景 &qu ...

  7. VC皮肤库SkinSharp 1.0.6.6的使用

    SkinSharp又称Skin#,是Windows环境下一款强大的换肤组件. SkinSharp作为换肤控件,仅仅须要在您的程序中加入一行代码,就能让您的界面焕然一新,并拥有多种主题风格和色调的动态切 ...

  8. Git 提供篇

    1. Git自动补全 假使你使用命令行工具运行Git命令,那么每次手动输入各种命令是一件很令人厌烦的事情.为了解决这个问题,你可以启用Git的自动补全功能,完成这项工作仅需要几分钟. 为了得到这个脚本 ...

  9. JavaScript ----------------- 原型式继承

    思想:借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型.为了达到这个目的,看看下面的实现方式 function object(o){ function F(){ } F.protot ...

  10. ASP.NET中多个相同name的控件在后台正确取值

    有兽,   页面上可能有多个相同name的Html表单控件,   一般在后台使用Request.Form[“name”]取值,并用‘,’分隔.   但是当值中包含逗号时,   取值就会出现异常,   ...