最近發現一個問題,在服務器上通過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()進行處理
可參考:http://wiki.apache.org/HttpComponents/FrequentlyAskedConnectionManagementQuestions
http://hc.apache.org/httpclient-legacy/tutorial.html
TCP/IP詳解卷一