問題描述:
生產環境突然之間出現了大量的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鑒權等。