最近,公司的接口服務器(客戶端,向外發送數據)頻繁出現了connect timeout 以及readtime out 的狀況,通過運維平臺檢測,並無網絡延時的狀況。因而,開始懷疑鏈接池出了問題。linux
使用linux命令: netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' 能夠清楚的看到tcp各個狀態下的鏈接數。web
如圖: CLOSE_WAIT 數目大的驚人,問題就出在了這裏:這個級別的TIME_WAIT是沒有問題的, linux的句柄數(https://blog.csdn.net/shootyou/article/details/6579139)有限,大量的CLOSE_WAIT佔去了過多的鏈接數,致使其餘鏈接異常。算法
據查:json
tcp鏈接三次握手,四次揮手。windows
這其中,咱們比較關注的狀態有三個: ESTABLISHED 表示正在通訊,TIME_WAIT 表示主動關閉,CLOSE_WAIT 表示被動關閉。服務器
其中:ESTABLISHED 無需多言, 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的狀況。app
而後,我又在windows 端實時測試(netstat一樣適用),的確,我所使用的HttpClient4 在每次接口發送完畢都會產生一個CLOSE_WAIT。這是爲何呢,使用鏈接池,是爲了鏈接能夠複用,而如今的狀況確是相反的。運維
經查,HttpClient4爲了鏈接複用使用的都是長鏈接,Response的Header中默認Connection是Keep-alive即鏈接不會關閉,以便下次請求相同網站的時候進行復用,這是產生CLOSE_WAIT鏈接的緣由所在。因此我猜想HttpClient4 故意留住CLOSE_WAITtcp
複用該鏈接。不會複用? 是否是個人對象建立的有問題?因而改了單例:
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; } } } }
問題得以解決。
水比較深,未完待續。