最近,公司的接口服务器(客户端,向外发送数据)频繁出现了connect timeout 以及readtime out 的情况,经过运维平台检测,并没有网络延时的情况。于是,开始怀疑连接池出了问题。
使用linux命令: netstat -n | awk ‘/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}‘ 可以清楚的看到tcp各个状态下的连接数。
如图: CLOSE_WAIT 数目大的惊人,问题就出在了这里:这个级别的TIME_WAIT是没有问题的, linux的句柄数(https://blog.csdn.net/shootyou/article/details/6579139)有限,大量的CLOSE_WAIT占去了过多的连接数,导致其他连接异常。
据查:
tcp连接三次握手,四次挥手。
这其中,我们比较关注的状态有三个: ESTABLISHED 表示正在通信,TIME_WAIT 表示主动关闭,CLOSE_WAIT 表示被动关闭。
其中:ESTABLISHED 无需多言, TIME_WAIT 的存在是:
- 防止上一次连接中的包,迷路后重新出现,影响新连接(经过2MSL,上一次连接中所有的重复包都会消失)
- 可靠的关闭TCP连接。在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。另外这么设计TIME_WAIT 会定时的回收资源,并不会占用很大资源的,除非短时间内接受大量请求或者受到攻击。
值 得一说的是,对于基于TCP的HTTP协议,关闭TCP连接的是Server端,这样,Server端会进入TIME_WAIT状态,可 想而知,对于访 问量大的Web Server,会存在大量的TIME_WAIT状态,假如server一秒钟接收1000个请求,那么就会积压 240*1000=240,000个 TIME_WAIT的记录,维护这些状态给Server带来负担。当然现代操作系统都会用快速的查找算法来管理这些 TIME_WAIT,所以对于新的 TCP连接请求,判断是否hit中一个TIME_WAIT不会太费时间,但是有这么多状态要维护总是不好。 ?HTTP协议1.1版规定default行为是Keep-Alive,也就是会重用TCP连接传输多个 request/response,一个主要原因就是发现了这个问题。 ?
也就是说HTTP的交互跟上面画的那个图是不一样的,关闭连接的不是客户端,而是服务器,所以web服务器也是会出现大量的TIME_WAIT的情况的。这也说清楚了,为什么我的客户端服务器会出现大量CLOSE_WAIT的情况。
然后,我又在windows 端实时测试(netstat同样适用),的确,我所使用的HttpClient4 在每次接口发送完毕都会产生一个CLOSE_WAIT。这是为什么呢,使用连接池,是为了连接可以复用,而现在的情况确是相反的。
经查,HttpClient4为了连接复用使用的都是长连接,Response的Header中默认Connection是Keep-alive即连接不会关闭,以便下次请求相同网站的时候进行复用,这是产生CLOSE_WAIT连接的原因所在。所以我猜测HttpClient4 故意留住CLOSE_WAIT
复用该连接。不会复用? 是不是我的对象创建的有问题?于是改了单例:
public class HttpApacheClient { ???private static class SingletonHandler{ ???????private static HttpApacheClient singleton = new HttpApacheClient(); ???} ???private HttpApacheClient(){} ???public static HttpApacheClient getInstance(){ ???????return SingletonHandler.singleton; ???} ???private static Logger logger = LoggerFactory.getLogger(HttpApacheClient.class); ???private final HttpClientConnectionManager manager = builderPoolConnectionManager() ; ?//定义连接池管理变量 ???ResponseHandler<String> responseHandler = new ResponseHandler<String>() { ???????@Override ???????public String handleResponse(final HttpResponse response) ???????????????throws ClientProtocolException, IOException { ???????????int status = response.getStatusLine().getStatusCode(); ???????????if (status >= 200 && status < 300) { ???????????????HttpEntity entity = response.getEntity(); ???????????????if (entity == null) throw new BusinessException(RESULT_LOG_ERROR); ???????????????String content = ?EntityUtils.toString(entity); ???????????????logger.info("the log back :"+content); ???????????????JSONObject jsonObject = JSON.parseObject(content); ???????????????Integer returnStatus = (Integer)jsonObject.get("status"); ???????????????if (returnStatus !=200) throw new BusinessException(RESULT_LOG_ERROR,content); ???????????????return content; ???????????} else { ???????????????throw new ClientProtocolException("Unexpected response status: " + status); ???????????} ???????} ???}; ???public HttpClientConnectionManager builderPoolConnectionManager(){ ???????final SSLContext context = SSLContexts.createSystemDefault(); ???????final HostnameVerifier verifier = new DefaultHostnameVerifier() ; ???????//自定义注册器,既可以发送http请求,也可以发送https请求 ???????final Registry<ConnectionSocketFactory> register = RegistryBuilder.<ConnectionSocketFactory>create() ???????????????.register("http" , PlainConnectionSocketFactory.INSTANCE) ???????????????.register("https" , new SSLConnectionSocketFactory(context ,verifier)) ???????????????.build() ; ???????PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(register); ???????poolingHttpClientConnectionManager.setMaxTotal(200); ?//设置连接池的最大连接数 ???????poolingHttpClientConnectionManager.setDefaultMaxPerRoute(poolingHttpClientConnectionManager.getMaxTotal()); ?//一个路由的最大连接数 ???????return poolingHttpClientConnectionManager ; ???} ???public HttpClient buildHttpClient(){ ???????RequestConfig config = RequestConfig.custom() ???????????????.setConnectionRequestTimeout(3000) ?//从池中获取请求的时间 ???????????????.setConnectTimeout(2000) ??//连接到服务器的时间 ???????????????.setSocketTimeout(5000).build(); //读取信息时间 ???????CloseableHttpClient build = HttpClients.custom() ???????????????.setRetryHandler(DefaultHttpRequestRetryHandler.INSTANCE) ???????????????.setDefaultRequestConfig(config) ???????????????.setConnectionManagerShared(true) ???????????????.setConnectionManager(manager) ???????????????.build(); ???????return build ; ???} ???public static String postJson(String url, List<NameValuePair> params) throws Exception { ???????String urlHead = (String) BeanHelper.getConfig("url"); ???????url = urlHead+url; ???????logger.info("url "+url+";"+params); ???????HttpApacheClient utils = getInstance(); ???????HttpClient httpClient = null; ???????HttpPost httpPost = null; ???????try { ???????????httpClient = utils.buildHttpClient(); ???????????httpPost = new HttpPost(url); ???????????httpPost.setEntity(new UrlEncodedFormEntity(params,"UTF-8")); ???????????httpPost.setHeader("Content-type", "application/x-www-form-urlencoded"); ???????????return httpClient.execute(httpPost, utils.responseHandler); ???????}catch (Exception e){ ???????????if (httpPost!=null) httpPost.abort();//异常时 关闭连接 ???????????throw ?e; ???????} ???} ???}}
问题得以解决。
水比较深,未完待续。
HttpClient4 TIME_WAIT和CLOSE_WAIT
原文地址:https://www.cnblogs.com/caoyusongnet/p/9087633.html