httpclient的並發連接問題


昨天的搜索系統又出狀況了,幾個庫同時重建索引變得死慢。經過一個上午的復現分析,確定問題出現httpclient的使用上(我使用的是3.1這個被廣泛使用的遺留版本)。搜索系統在重建索引時,是並發多個線程(默認是8個)不停的從PHP客戶端取數據(當然,從另一個角度來說,搜索系統是客戶端,PHP端是服務端),取回后放到一個隊列里由單獨的一個或多個線程更新索引。在測試環境復現發現,對於一個請求,PHP端打印耗時是1-2秒,但搜索端打印在4-6秒。這種耗時差別也就兩種可能性,一個是PHP端返回到搜索端接受完耗時太長,另一個就是搜索端在真正發給PHP端數據前等待了很久。因為有了之前的jetty7的困頓,起初我懷疑是傳輸數據的問題。因為請求數據的代碼部分我只是簡單的使用了httpclient,所以只能從httpclient着手分析。我想到把PHP端和搜索端的請求起始和結束時間都打出來對照一下,不過在這樣做之前我把搜索端的並發請求線程數調到了1,看下單線程情況下效果如何,結果驚奇地發現PHP端和搜索端的耗時相近。所以,可以確定,是httpclient的並發連接處理上可能存在問題。不消說,翻開httpclient API中和配置相關的接口,結果找到HttpConnectionManagerParams類中下面兩個函數:

public void setDefaultMaxConnectionsPerHost(int maxHostConnections); public void setMaxTotalConnections(int maxTotalConnections);

httpclient在處理請求連接方面使用了連接池,它內部實際上有兩種連接池,一種是全局的ConnectionPool,一種是每主機(per-host)HostConnectionPool。參數maxHostConnections就HostConnectionPool中表示每主機可保持連接的連接數,maxTotalConnections是ConnectionPool中可最多保持的連接數。每主機的配置類是HostConfiguration,HttpClient有個int executeMethod(final HostConfiguration hostConfiguration, final HttpMethod method)方法可以指定使用哪個HostConfiguration,不過多數情況都是不顯示指定HostConfiguration,這樣httpclient就用了默認的HostConfiguration=null,也就是說所有的請求可以認為指自同一個主機。如果不設置這兩個參數,httpclient自然會用默認的配置,也就是MultiThreadedHttpConnectionManager中的:

public static final int DEFAULT_MAX_HOST_CONNECTIONS = 2; // Per RFC 2616 sec 8.1.4 ? public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 20;

默認的maxHostConnections大小只有2,也就是說,在我並發8個線程請求數據時,實際上會有6個線程處於等待被調度,這也就解釋上面的現象了。再看看上面的注釋,我從http://www.faqs.org/rfcs/rfc2616.html找到從了RFC 2616 sec 8.1.4 Practical Considerations段落的最后一段:

Clients that use persistent connections SHOULD limit the number of simultaneous connections that they maintain to a given server. A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy. A proxy SHOULD use up to 2*N connections to another server or proxy, where N is the number of simultaneously active users. These guidelines are intended to improve HTTP response times and avoid congestion.

看這敘述,也就表明人家httpclient設置maxHostConnections為2是有根有據的。不過,這種設置顯然適合的是瀏覽器這種客戶端,但我相信,多數使用httpclient並不希望有這種默認的限制。而它默認的只有20的maxTotalConnections也太有些吝嗇了。我后來瀏覽solr的客戶端server類CommonsHttpSolrServer的代碼發現了下面的段落,solr要比我更了解httpclient:

_httpClient = (client == null) ? new HttpClient(new MultiThreadedHttpConnectionManager()) : client; if (client == null) { // set some better defaults if we created a new connection manager and client // increase the default connections this.setDefaultMaxConnectionsPerHost( 32 ); // 2 this.setMaxTotalConnections( 128 ); // 20 }

對於httpclient,特別指出的是它的MultiThreadedHttpConnectionManager,看名字好像是多線程並發請求似的,其實不是,但它也確實用到了多線程,那是在發現連接不夠用時起個等待線程wait信號,這個名稱的含義應該是多線程情況線程安全的HttpConnectionManager。
使用httpclient還有兩點經驗,一個是創建的MultiThreadedHttpConnectionManager 實例最好是全局的,否則會有多個連接池,而HttpClient是線程安全的,可以多個實例。另一個是,在處理請求的最后,也就是finnaly里中,要調用method.releaseConnection();回收連接,否則連接池就可能會爆了。

補充:寫完之后倒在床上,我又想起了幾個問題,這里補充上:
1、系統原先重建索引隱約記得速度還是可以的,為什么現在變慢得如此明顯?這有兩方面的原因,一個是原來系統取數據是用的單線程(我后來發現單取數據取數據速度跟不上更新索引的速度所以改成多線程),另一個是,當時重建沒有一下子同時開啟數個庫。所以,即便是同樣的代碼,環境變了,效果也可能變了。當這種改變悄然發生了,程序員卻沒有捕捉到,才會第一直覺感到問題的詭異。
2、對於長時間不能獲得連接的情況,httpclient是否有warn日志報出來?因為我使用了httpclient的getResponseBodyAsStream方法,而它會打出warn日志,所以我是關掉了httpclient的warn級別的。所以,我又檢查了httpclient的代碼,可惜沒看到相關warning log,這點httpclient可以改進下。不過httpclient現在都是4時代了,而我使用的還是3.1的,而3.x已經被停止更新了,所以再采用httpclient可以考慮4版本的,盡管現在能見到的代碼幾乎都是用的3.x系列的。
3、httpclient的文檔是否有特別提到連接數配置的情況?我翻看了一下,確實在關於threading一頁中有提到。不過,我等使用它時顯然沒有完整閱畢它的文檔。或許,httpclient給出個明顯的最佳實踐到能引起使用者的注意,否則誤用的情況還是會時有發生。不信,google之。


免責聲明!

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



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