转自: 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. Shell函数返回值、删除函数、在终端调用函数

    Shell 也支持函数.Shell 函数必须先定义后使用. Shell 函数的定义格式如下: function_name () { list of commands [ return value ] ...

  2. 快速批量导入庞大数据到SQL SERVER数据库(ADO.NET)

    原文地址:http://www.cnblogs.com/chenxizhang/archive/2008/11/11/1331060.html 如果你需要在程序中批量插入成千上万行的数据,你会怎么编写 ...

  3. Palindrome Linked List 解答

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

  4. Linux Java的环境变量搭建

    JAVA JDK:http://www.oracle.com/technetwork/java/javase/downloads/index.html 下载完后,解压完并将其中的jdk文件夹移动到/u ...

  5. pyqt MainWindow记录内容

    class Texts(QtGui.QMainWindow,Ui_MainWindow): def __init__(self,parne=None): super(Texts,self).__ini ...

  6. C基础知识小总结(十)

                  "如有不正确之处,请指出,谢谢"    --Mood   <指针和函数> 指针函数   函数指针 <最基本的使用函数指针>   ...

  7. debian安装mysql

    http://thirteen-tw.blogspot.com/2008/09/debian-mysql-server.html 安裝MySQL-Server debian:~# apt-get in ...

  8. IoC容器Autofac之IOC/DI基本概念(二)

    原文:http://www.cnblogs.com/xdp-gacl/p/4249939.html 1.1.IoC是什么 Ioc—Inversion of Control,即“控制反转”,一种设计思想 ...

  9. HTML与CSS入门——第二章 发布Web内容

    知识点: 1.使用文本编辑器创建一个基本的HTML文件的方法 2.使用FTP将文件传送到你的Web服务器的方法 3.文件在Web服务器上应该存储的位置 4.在没有Web服务器的情况下分发Web内容的方 ...

  10. js中的eval方法转换对象时,为何一定要加上括号?

    待转换的是一个Json字符串: {'name':'新欢'} 而使用如下这种方式调用则会抛出语法异常, eval("{'name':'新欢'}"); 必须加上括号才行 eval(&q ...