java httpclient4.3连接池怎么解决大量close_wait的问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java httpclient4.3连接池怎么解决大量close_wait的问题相关的知识,希望对你有一定的参考价值。

最近发现一个问题,在服务器上通过netstat命令发现有大量的Close_Wait长时间存在,甚至有时候数量接近1000:

查看服务器参数(etc/sysctl.conf):

net.ipv4.tcp_keepalive_time 网管已经修改成1200。

参数值还可以改小,但似乎是治标不治本,出现这种问题,肯定是某个地方的程序本身存在问题。

根据ip及端口信息,不难发现是什么地方除问题了,项目中有涉及到图片上传,于是找到图片上传的代码,结果发现代码非常简单,一行上传权限初始化代码,一行CDN官方提供的一个静态方法,之后就是处理响应结果的代码了。代码少且简单,上传调用代码没什么问题,那么问题可能出在CDN官方提供的jar包了,好在CDN有提供源码,于是查看源码,源码中使用apache 的是httpClient包,调用代码大致如下:

String response = "";
HttpPost httpPost = null;
CloseableHttpResponse ht = null;
String startTime = formatter.format(new Date());//请求时间
String endTime = "-";
String statusCode = "-";
String contentLength = "-";
String contentType = "-";        try
httpPost = new HttpPost(url);
List<NameValuePair> paramsList = new ArrayList<NameValuePair>();            if (file != null)
MultipartEntityBuilder mEntityBuilder = MultipartEntityBuilder.create();

BandwithLimiterFileBody fileBody = new BandwithLimiterFileBody(file, null, "application/octet-stream", null, BaseBlockUtil.maxRate, progressNotifier);
mEntityBuilder.addPart("file", fileBody);
mEntityBuilder.addTextBody("desc", file.getName());                if (params != null && params.size() > 0)                    for (String name : params.keySet())
mEntityBuilder.addTextBody(name, params.get(name), ContentType.create("text/plain", Charset.forName("UTF-8")));



httpPost.setEntity(mEntityBuilder.build());
else if (params != null && params.size() > 0)                for (String name : params.keySet())
paramsList.add(new BasicNameValuePair(name, params.get(name)));

HttpEntity he = new UrlEncodedFormEntity(paramsList, "utf-8");
httpPost.setEntity(he);
           if (headMap != null && headMap.size() > 0)                for (String name : headMap.keySet())
httpPost.addHeader(name, headMap.get(name));

           if(!httpPost.containsHeader("User-Agent"))
httpPost.addHeader("User-Agent", Config.VERSION_NO);
CloseableHttpClient hc = HttpClients.createDefault();
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(30000).setConnectTimeout(30000).build();//设置请求和传输超时时间            httpPost.setConfig(requestConfig);
ht = hc.execute(httpPost);
endTime = formatter.format(new Date());

Header[] headerArr = ht.getAllHeaders();            for (Header header : headerArr)
BufferedHeader bh = (BufferedHeader) header;                if (bh.getBuffer().toString().contains("Content-Length"))
contentLength = bh.getValue();
else if (bh.getBuffer().toString().contains("Content-Type"))
contentType = bh.getValue();


HttpEntity het = ht.getEntity();
InputStream is = het.getContent();
BufferedReader br = new BufferedReader(new InputStreamReader(is, "utf8"));
String readLine;            while ((readLine = br.readLine()) != null)
response = response + readLine;


is.close();
br.close();            int status = ht.getStatusLine().getStatusCode();
statusCode = String.valueOf(status);            if (status == 200)                if (!new JsonValidator().validate(response))
response = EncodeUtils.urlsafeDecodeString(response);

           return new HttpClientResult(status, response);
catch (Exception e)
statusCode = "500";
endTime = formatter.format(new Date());            throw new HttpClientException(e);
finally            if (httpPost != null)
httpPost.releaseConnection();
           if (ht != null)                try
ht.close();
catch (IOException ignored)


writeHttpLog(startTime, url, "-", (null != params ? params.get("token") : "-"), (null != file ? file.getName() : "-"), "-", "-", endTime, statusCode, contentType, contentLength, response);

查看TCP协议端口状态说明 , 如果一直保持在CLOSE_WAIT状态,那么只有一种情况,就是在对方关闭连接之后服务器程序自己没有进一步发出ack信号。因此要解决这个问题大致有以下几种方案:

a、实例化httpClient 时,使用alwaysClose 的SimpleHttpConnectionManager

通常默认情况实例化httpClient 的时候使用的是无参构造的SimpleHttpConnectionManager,因此可替换为带参构造:

new HttpClient(new SimpleHttpConnectionManager(true));

b、在method.releaseConnection() 之后 通过获取HttpConnectionManager,进行关闭(getConnectionManager方法在httpclient 4.3之后已经标记为过期,后期可能会移除该方法):

hc.getConnectionManager().shutdown();

c、在method.releaseConnection() 之后 通过获取HttpConnectionManager 调用closeIdleConnections方法进行关闭,(getConnectionManager方法在httpclient 4.3之后已经标记为过期,后期可能会移除该方法):

hc.getConnectionManager().releaseConnection(conn, validDuration, timeUnit);

d、通过设置header由服务端自动关闭.

method.setHeader("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 

e、使用MultiThreadedHttpConnectionManager 进行复用,但同时也必须在合适的地方进行关闭处理;

f、请求之后未收到响应信息时,调用method.abort()进行处理

参考技术A

可以试试使用完后关闭连接

httpClient.getConnectionManager().shutdown();

参考技术B 估计你的问题和这个问题是一样的。
http://blog.csdn.net/shootyou/article/details/6615051追问

看过了,加上request.abort();也没效果,还是很多close_wait,而且经常报这个错Software caused connection abort: recv failed

追答

网络是不是不稳定的啊,可以的话把相关的代码贴上来看下吧

使用 HttpClient 的 HTTP 连接池

【中文标题】使用 HttpClient 的 HTTP 连接池【英文标题】:Connection pooling in java using HttpClient [closed] 【发布时间】:2011-06-18 14:27:16 【问题描述】: 如何使用 HttpClient 创建连接池? 我必须频繁连接到同一台服务器。是否值得创建这样一个池? 是否可以保持实时连接并将其用于各种请求,如果可以,我该怎么做?

我正在使用 Java 进行开发,使用 Apache HTTP Client。

【问题讨论】:

很好的阅读主题:baeldung.com/httpclient-connection-management 【参考方案1】:

[假设 Java 和 Apache 的 HttpClient]

使用ThreadSafeClientConnManager。将单个全局实例传递给每个 HttpClient 实例的构造函数。我认为汇集 HttpClients 本身没有任何意义。

【讨论】:

后来换成了org.apache.http.impl.conn.PoolingHttpClientConnectionManager 所有方法和构造函数在 ThreadSafeClientConnManager 的文档中都被标记为 deprecated【参考方案2】:

对于 HttpClient 4x:

ThreadSafeClientConnManager ...管理一个客户端池 连接,并且能够为来自多个执行线程的连接请求提供服务。

连接按路由合并。对经理已经为之的路线的请求 在池中有一个可用的持久连接将通过租用 连接来提供服务 池,而不是创建一个全新的连接。

http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html

【讨论】:

【参考方案3】:

ThreadSafeClientConnManager 现已弃用,请改用PoolingClientConnectionManager。

【讨论】:

PoolingClientConnectionManager 已弃用,请使用 PoolingHttpClientConnectionManager(2015 年起您好) ***.com/questions/15336477/…【参考方案4】:

PoolingClientConnectionManager 现已弃用。从(4.3版本)使用PoolingHttpClientConnectionManager

【讨论】:

类似话题:***.com/questions/15336477/…【参考方案5】:

这是一个不需要身份验证的 Apache HttpClient 4.3 连接池示例:

public class PoolOfHttpConnections
   static String[] urisToGet = "http://www.site1.com", "http://www.site2.com";

    public static void main(String[] args) throws Exception 
           CloseableHttpClient httpclient = HttpClients.createDefault();
           // create a thread for each link
           GetThread[] threads = new GetThread[urisToGet.length];
           for (int i = 0; i < threads.length; i++) 
               HttpGet httpget = new HttpGet(urisToGet[i]);
               threads[i] = new GetThread(httpClient, httpget);
           

           // start the threads
           for (int j = 0; j < threads.length; j++) 
               threads[j].start();
           
           // join the threads
           for (int j = 0; j < threads.length; j++) 
               threads[j].join();
           
     //end main

    private static class GetThread extends Thread 

            private final CloseableHttpClient httpClient;
            private final HttpContext context;
            private final HttpGet httpget;

            public GetThread(CloseableHttpClient httpClient, HttpGet httpget) 
                   this.httpClient = httpClient;
                   this.context = HttpClientContext.create();
                   this.httpget = httpget;
            

            @Override
            public void run() 
                   try 
                       CloseableHttpResponse response = httpClient.execute(httpget, context);
                       try 
                           HttpEntity entity = response.getEntity();
                           System.out.println("----------------------------------------");
                           Date date = new Date();
                           System.out.println("Beginning*******************");
                           System.out.println(date.toString());
                           System.out.println("There are "+urisToGet.length+" threads running in parallel!");
                           System.out.println(response.getStatusLine());
                           if (entity != null) 
                              System.out.println("Response content length: " + entity.getContentLength());
                           
                           System.out.println(EntityUtils.toString(entity));
                           EntityUtils.consume(entity);
                        finally 
                         response.close();
                         System.out.println("End*******************");
                       
                    catch (ClientProtocolException ex) 
                          // Handle protocol errors
                    catch (IOException ex) 
                          // Handle I/O errors
                   
            
     /*end private class*/  //end public class PoolOfHttpConnections

【讨论】:

【参考方案6】:

HttpClient 已经有一个连接池,所以你不需要创建它。随便用吧。

【讨论】:

这绝对是错误的。 Httpclient默认没有连接池【参考方案7】:

我最近几天都在研究这个,所以只想和你分享一些“众所周知”的知识。

首先,由于您处理的是同一台服务器,因此建议使用单个 HTTP 客户端来执行您的请求。在PoolingHttpClientConnectionManager 的帮助下,您的客户端可用于同时执行多个请求。多线程请求执行的官方示例可见here。

其次,HTTP/1.1(和 HTTP/1.0 的增强版本)允许 HTTP 客户端在事务完成后保持连接打开,以便可以将其重用于未来的请求。这通常称为持久连接

同样为了对多个请求重用客户端,来自服务器的响应头通常包含一个属性调用Keep-Alive,其中包含当前连接保持活动的时间。除此之外,Apache Http Client 还为您提供了一个接口ConnectionKeepAliveStrategy来自定义您自己的重用连接策略。

【讨论】:

只是想知道,什么时候可以在ConnectionConfig 中为4.x 之前的每个HttpClient 设置连接超时和套接字超时,现在它们在PoolingHttpClientConnectionManager 中不存在。现在,如何设置超时?还有,在哪里设置http.conn-manager.timeout 是在获得连接之前等待的时间?

以上是关于java httpclient4.3连接池怎么解决大量close_wait的问题的主要内容,如果未能解决你的问题,请参考以下文章

HttpClient 4.3连接池参数配置及源码解读

HttpClient4.3教程 第二章 连接管理

java连接redis超时问题怎么解决

java数据库连接池最大连接数最小连接数怎么设置

java数据库连接池最大连接数最小连接数怎么设置

httpclient4.3 工具类