• HttpClient当HTTP连接的时候出现大量CLOSE_WAIT连接


    三种状态:

    ESTABLISHED 表示正在进行网络连接的数量
    TIME_WAIT 表示表示等待系统主动关闭网络连接的数量
    CLOSE_WAIT 表示被动等待程序关闭的网络连接数量

    上篇文章给出了解决TIME_WAIT太多的方法,本篇文章以HttpClient为例说明解决大量CLOSE_WAIT状态的方法。

    HttpClient是大量使用的用于HTTP连接的包,首先需要说明的是HttpClient 3.x和4.x之间API差距很多,不过强烈建议使用4.x的版本。除此之外,4.x中每个x之间也有一些差别(比如一些弃用的类,新增加的类等),这里以4.2.3版本进行说明。

    HttpClient使用的HTTP 1.1协议进行连接,相对于HTTP 1.0来说有一个持续连接的增强,为了充分利用持续连接的特性,在一次连接结束之后,即使将HttpResponse使用close方法关闭,并且将调用了HttpGet或HttpPost的releaseConnection方法,示例代码如下:

    HttpGet method = null;
    HttpResponse response = null;
    try {
        method = new HttpGet(url);
        response = client.execute(method);
    } catch(Exception e) {
    
    } finally {
        if(response != null) {
            EntityUtils.consumeQuietly(response.getEntity());
        }
        if(method != null) {
            method.releaseConnection();
        }
    }

    这个时候仍然发现连接处于CLOSE_WAIT状态,这是因为HttpClient在执行close的时候,如果发现Response的Header中Connection是Keep-alive则连接不会关闭,以便下次请求相同网站的时候进行复用,这是产生CLOSE_WAIT连接的原因所在。

    最简单的一种解决方法在execute方法之前增加Connection: close头信息,HTTP协议关于这个属性的定义如下:

    HTTP/1.1 defines the "close" connection option for the sender to signal that the connection will be closed after completion of the response. For example:
        Connection: close 
    

    示例代码如下:

    HttpGet method = null;
    HttpResponse response = null;
    try {
        method = new HttpGet(url);
        method.setHeader(HttpHeaders.CONNECTION, "close");
        response = client.execute(method);
    } catch(Exception e) {
    
    } finally {
        if(response != null) {
            EntityUtils.consumeQuietly(response.getEntity());
        }
        if(method != null) {
            method.releaseConnection();
        }
    }

    当然,也有人建议每次请求之后关闭client,但这一点不符合HttpClient设计的原则——复用。如果每次连接完成之后就关闭连接,效率太低了。因此,需要使用PoolingClientConnectionManager,并且设置maxTotal(整个连接池里面最大连接数,默认为20)和defaultMaxPerRoute(每个主机的最大连接数,默认为2),另外client还有一个ClientPNames.CONN_MANAGER_TIMEOUT参数,用来设置当连接不够获取新连接等待的超时时间,默认和CoreConnectionPNames.CONNECTION_TIMEOUT相同。可以根据实际情况对PoolingClientConnectionManager进行设置,以达到效率最优。

    还有一种情况也会造成大量CLOSE_WAIT连接,即HttpResponse的状态码不是200的时候,需要及时调用method.abort()方法对连接进行释放

    =====================================================

    HttpClient连接池抛出大量ConnectionPoolTimeoutException: Timeout waiting for connection异常排查

    =====================================================

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

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

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

    问题表现:

    tomcat后台日志发现大量异常

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

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

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

    public class HttpConnectionManager {

    private static HttpParams httpParams;
    private static ClientConnectionManager connectionManager;

    /**
    * 最大连接数
    */
    public final static int MAX_TOTAL_CONNECTIONS = 800;
    /**
    * 获取连接的最大等待时间
    */
    public final static int WAIT_TIMEOUT = 60000;
    /**
    * 每个路由最大连接数
    */
    public final static int MAX_ROUTE_CONNECTIONS = 400;
    /**
    * 连接超时时间
    */
    public final static int CONNECT_TIMEOUT = 10000;
    /**
    * 读取超时时间
    */
    public final static int READ_TIMEOUT = 10000;

    static {
    httpParams = new BasicHttpParams();
    // 设置最大连接数
    ConnManagerParams.setMaxTotalConnections(httpParams, MAX_TOTAL_CONNECTIONS);
    // 设置获取连接的最大等待时间
    ConnManagerParams.setTimeout(httpParams, WAIT_TIMEOUT);
    // 设置每个路由最大连接数
    ConnPerRouteBean connPerRoute = new ConnPerRouteBean(MAX_ROUTE_CONNECTIONS);
    ConnManagerParams.setMaxConnectionsPerRoute(httpParams,connPerRoute);
    // 设置连接超时时间
    HttpConnectionParams.setConnectionTimeout(httpParams, CONNECT_TIMEOUT);
    // 设置读取超时时间
    HttpConnectionParams.setSoTimeout(httpParams, READ_TIMEOUT);

    SchemeRegistry registry = new SchemeRegistry();
    registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
    registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));

    connectionManager = new ThreadSafeClientConnManager(httpParams, registry);
    }

    public static HttpClient getHttpClient() {
    return new DefaultHttpClient(connectionManager, httpParams);
    }

    }


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

    然后看看调用它的代码是什么样的:
    public static String readNet (String urlPath)
    {
    StringBuffer sb = new StringBuffer ();
    HttpClient client = null;
    InputStream in = null;
    InputStreamReader isr = null;
    try
    {
    client = HttpConnectionManager.getHttpClient();
    HttpGet get = new HttpGet();
    get.setURI(new URI(urlPath));
    HttpResponse response = client.execute(get);
    if (response.getStatusLine ().getStatusCode () != 200) {
    return null;
    }
    HttpEntity entity =response.getEntity();

    if( entity != null ){
    in = entity.getContent();
    .....
    }
    return sb.toString ();

    }
    catch (Exception e)
    {
    e.printStackTrace ();
    return null;
    }
    finally
    {
    if (isr != null){
    try
    {
    isr.close ();
    }
    catch (IOException e)
    {
    e.printStackTrace ();
    }
    }
    if (in != null){
    try
    {
    in.close ();
    }
    catch (IOException e)
    {
    e.printStackTrace ();
    }
    }
    }
    }

    很简单,就是个远程读取中文页面的方法。值得注意的是这一段代码是后来某某同学加上去的,看上去没啥问题,是用于非200状态的异常处理:
    if (response.getStatusLine ().getStatusCode () != 200) {
    return null;
    }

    代码本身没有问题,但是问题是放错了位置。如果这么写的话就没问题:
    client = HttpConnectionManager.getHttpClient();
    HttpGet get = new HttpGet();
    get.setURI(new URI(urlPath));
    HttpResponse response = client.execute(get);

    HttpEntity entity =response.getEntity();

    if( entity != null ){
    in = entity.getContent();
    ..........
    }

    if (response.getStatusLine ().getStatusCode () != 200) {
    return null;
    }
    return sb.toString ();
    看出毛病了吧。在这篇入门(HttpClient4.X 升级 入门 + http连接池使用)里头我提到了HttpClient4使用我们常用的InputStream.close()来确认连接关闭,前面那种写法InputStream in 根本就不会被赋值,意味着一旦出现非200的连接,这个连接将永远僵死在连接池里头,太恐怖了。。。所以我们看到CLOST_WAIT数目为400,因为对一个路由的连接已经完全被僵死连接占满了。。。
    其实上面那段代码还有一个没处理好的地方,异常处理不够严谨,所以最后我把代码改成了这样:

    public static String readNet (String urlPath)
    {
    StringBuffer sb = new StringBuffer ();
    HttpClient client = null;
    InputStream in = null;
    InputStreamReader isr = null;
    HttpGet get = new HttpGet();
    try
    {
    client = HttpConnectionManager.getHttpClient();
    get.setURI(new URI(urlPath));
    HttpResponse response = client.execute(get);
    if (response.getStatusLine ().getStatusCode () != 200) {
    get.abort();
    return null;
    }
    HttpEntity entity =response.getEntity();

    if( entity != null ){
    in = entity.getContent();
    ......
    }
    return sb.toString ();

    }
    catch (Exception e)
    {
    get.abort();
    e.printStackTrace ();
    return null;
    }
    finally
    {
    if (isr != null){
    try
    {
    isr.close ();
    }
    catch (IOException e)
    {
    e.printStackTrace ();
    }
    }
    if (in != null){
    try
    {
    in.close ();
    }
    catch (IOException e)
    {
    e.printStackTrace ();
    }
    }
    }
    }

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

  • 相关阅读:
    linux(CENTOS)系统各个目录的作用详解
    2018 焦作E java 高精度暴力
    [SHOI2015]激光发生器,计算几何 直线相交
    codeforces 600E dfs+线段树合并
    2018 南京区域赛A SG打表
    8个常见的硬币博弈的SG值规律
    hdu 3389 阶梯博弈
    组合游戏与博弈好文
    gym 100500B 多项式哈希+Rabbin-Karp/最小表示法
    zjoi 2007 捉迷藏 动态点分治+可删堆
  • 原文地址:https://www.cnblogs.com/sea520/p/12612621.html
Copyright © 2020-2023  润新知