由於httpClient調用導致的ESTABLISHED過多和 Connection rest by peer 異常


問題描述:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM