• 由于httpClient调用导致的ESTABLISHED过多和 Connection rest by peer 异常


    问题描述:

    生产环境突然之间出现了大量的Connection rest by peer.后来使用netstat -an | grep 服务端口号发现有大量来自A10服务器的ESTABLISHED连接,多的时候单台达到1万多,总连接数达到3万多。后来查看A10服务器发现,连接数来自同一个客户,于是猜测可能是该用户在请求时,每一次都会建立一个新的连接,并且该连接没有释放。

    于是联系该用户,查看他们的建立请求连接的代码:

    发现客户使用了apache的httpClient的method.releaseConnection()来释放资源。经过同事的提醒说这样的释放资源其实是有问题的,于是在网上找到了解决相关介绍:

    先介绍一下,做了哪些工作以及后期的改正工作:

    1.首先在A10限制每一个ip可以访问的连接数,

    2.在A10配置所有的连接请求为短连接

    3.代码层面,通知客户修改其调用程序

    4.在服务层面应该禁止长连接的建立,都保持为短连接。

    以下是httpClient的使用介绍:

    http://www.myexception.cn/internet/1552774.html

    HttpClient引起的TCP连接数高的问题分析

    【问题现象】

    系统上线后出现TCP连接数超过预期阀值,最高值达到8K左右,新上线代码中包含了一文件上传操作,使用的是apache的commons-httpclient包。

    【问题分析】

    1、先确认是否存在连接未关闭问题引起的。

    观察发现,TCP连接数不是一直在增长,而是会有所下降。并且当业务低峰期TCP连接数TCP连接数会降到100左右,这说明TCP连接还是会关闭。

    2、确定居高不下的TCP使用情况

    使用"netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'"命令发现,处于ESTABLISHED状态的连接数最多,在查看了一下处于ESTABLISHED状态的目的IP,基本上都是文件服务器的 IP,这说明还是跟新增加的文件上传操作有关。但是按照代码的逻辑来看,文件上传操作是多线程处理的,一个线程处理一个上传操作,线程池中一共有10个线 程,照此分析正常的话应该有10个左右与文件服务器的链接,不应该出现几千个链接。因此怀疑是连接没有主动释放,而是等待连接超时才开始释放。

    3、为什么会连接超时

    查看了文件上传部分代码,主要代码如下:

    HttpClient client = new HttpClient();
    MultipartPostMethod method = new MultipartPostMethod(config .getUploadInterface());
    try{
        client.executeMethod(method);
    }catch (Exception e){
         throw e;    
    }finally{
         method.releaseConnection();
    }

     从代码里看是已经释放连接了,但是从结果上看没有释放连接,那就产生一个问题,这个地方真的能释放连接吗?我们在释放连接后面增加一行测试代码来看看:

    HttpConnection conn = client.getHttpConnectionManager().getConnection(client.getHostConfiguration());
    System.out.println(conn.isOpen());

     打印出的结果是true,也就是说虽然调用了releaseConnection,但是并没有释放连接!!

    4、分析commons-httpclient相关代码

    现在怀疑是我们使用的方式不对了,继续分析一下commons-https包中相关代码,首先看一下method.releaseConnection()的代码实现:

    public void releaseConnection() {
            try {
                if (this.responseStream != null) {
                    try {
                        // FYI - this may indirectly invoke responseBodyConsumed.
                        this.responseStream.close();
                    } catch (IOException ignore) {
                    }
                }
            } finally {
                ensureConnectionRelease();
            }
        }
    private void ensureConnectionRelease() {
            if (responseConnection != null) {
                responseConnection.releaseConnection();
                responseConnection = null;
            }
        }

     经过debug发现responseStream为null,并且responseConnection也为null,这样改调用就没有实际意义。那么我们应该怎么来释放连接呢?

    5、继续分析代码

    我们发现在org.apache.commons.httpclient.HttpMethodDirector类的第208行已经在finally中释放连接了:

    finally {
                if (this.conn != null) {
                    this.conn.setLocked(false);
                }
                // If the response has been fully processed, return the connection
                // to the pool.  Use this flag, rather than other tests (like
                // responseStream == null), as subclasses, might reset the stream,
                // for example, reading the entire response into a file and then
                // setting the file as the stream.
                if (
                    (releaseConnection || method.getResponseBodyAsStream() == null) 
                    && this.conn != null
                ) {
                    this.conn.releaseConnection();
                }
            }
    public void releaseConnection(HttpConnection conn) {
            if (conn != httpConnection) {
                throw new IllegalStateException("Unexpected release of an unknown connection.");
            }
    
            finishLastResponse(httpConnection);
            
            inUse = false;
    
            // track the time the connection was made idle
            idleStartTime = System.currentTimeMillis();
        }

     这个地方我们可以看到了所谓的释放连接并不是真的释放,还是return the connection to pool,照此分析,我们每个线程中new了一个HttpClient类,而每个HttpClient类中的链接都是没有close的,只是归还到 httpClient中的pool而已,这些连接也必须等到连接超时才会被释放,由此可以分析出来连接数上涨的原因。那么我们应该怎么使用呢?按照代码的 设计,看起来httpclient应该是单例的,但是在httpClient类的javadoc中并没有关于线程安全方面的说明,为此我们再回到官网上看 相关文档,在文档(http://hc.apache.org/httpclient-3.x/performance.html)上我们看到如下的说 明:

    HttpClient is fully thread-safe when used with a thread-safe connection manager such as MultiThreadedHttpConnectionManager

     这说明在多线程环境下应该使用一个全局单例的HttpClient,并且使用MultiThreadHttpConnectionManager来管理Connection。

    【相关结论】

    1、HttpClient内部使用了池化技术,内部的链接是为了复用。在多线程条件下,可以使用一个全局的HttpClient实例,并且使用MultiThreadHttpConnectionManager来管理Connection。

    2、使用开源软件之前一定要读读相关代码,看看官方推荐使用方式。

    3、在解决此问题后,读了读httpclient中其他包中的代码,在读的时候发现对于理解http协议帮助很大,特别是文件上传,长连接,auth鉴权等。

  • 相关阅读:
    .net解决跨域问题
    win7系统安装不了Visual Studio及sql server相关问题整理
    Visual Studio注释代码段快捷键
    实现文件下载,将后台返回的字节流转成下载链接
    表单中输入内容,搜索时,下面table中该列中包含关键字的高亮显示
    实现select联动效果,数据从后台获取
    vue的v-model指令
    SSM框架中,controller的action返回参数给vue.js
    vue路由实现多视图的单页应用
    关于vue的增删改查操作
  • 原文地址:https://www.cnblogs.com/zhangshiwen/p/5760589.html
Copyright © 2020-2023  润新知