- HttpClient client = new HttpClient();
- HttpMethod method = new GetMethod("http://www.apache.org");
- try {
- client.executeMethod(method);
- byte[] responseBody = null;
- responseBody = method.getResponseBody();
- } catch (HttpException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }finally{
- method.releaseConnection();
- }
大部分人使用HttpClient都是使用類似上面的事例代碼,包括Apache官方的例子也是如此。最近我在使用HttpClient是發現一次循環發送大量請求到服務器會導致APACHE服務器的鏈接被占滿,后續的請求便排隊等待。
我服務器端APACHE的配置
- Timeout 30
- KeepAlive On #表示服務器端不會主動關閉鏈接
- MaxKeepAliveRequests 100
- KeepAliveTimeout 180
因此這樣的配置就會導致每個鏈接至少要過180S才會被釋放,這樣在大量請求訪問時就必然會造成鏈接被占滿,請求等待的情況。
在通過DEBUH后發現HttpClient在method.releaseConnection()后並沒有把鏈接關閉,這個方法只是將鏈接返回給connection manager。如果使用HttpClient client = new HttpClient()實例化一個HttpClient connection manager默認實現是使用SimpleHttpConnectionManager。SimpleHttpConnectionManager有個構造函數如下
- /**
- * The connection manager created with this constructor will try to keep the
- * connection open (alive) between consecutive requests if the alwaysClose
- * parameter is set to <tt>false</tt>. Otherwise the connection manager will
- * always close connections upon release.
- *
- * @param alwaysClose if set <tt>true</tt>, the connection manager will always
- * close connections upon release.
- */
- public SimpleHttpConnectionManager(boolean alwaysClose) {
- super();
- this.alwaysClose = alwaysClose;
- }
看方法注釋我們就可以看到如果alwaysClose設為true在鏈接釋放之后connection manager 就會關閉鏈。在我們HttpClient client = new HttpClient()這樣實例化一個client時connection manager是這樣被實例化的
- this.httpConnectionManager = new SimpleHttpConnectionManager();
因此alwaysClose默認是false,connection是不會被主動關閉的,因此我們就有了一個客戶端關閉鏈接的方法。
方法一:
把事例代碼中的第一行實例化代碼改為如下即可,在method.releaseConnection();之后connection manager會關閉connection 。
- HttpClient client = new HttpClient(new HttpClientParams(),new SimpleHttpConnectionManager(true) );
方法二:
實例化代碼使用:HttpClient client = new HttpClient();
在method.releaseConnection();之后加上
- ((SimpleHttpConnectionManager)client.getHttpConnectionManager()).shutdown();
shutdown源代碼很簡單,看了一目了然
- public void shutdown() {
- httpConnection.close();
- }
方法三:
實例化代碼使用:HttpClient client = new HttpClient();
在method.releaseConnection();之后加上
client.getHttpConnectionManager().closeIdleConnections(0);此方法源碼代碼如下:
- public void closeIdleConnections(long idleTimeout) {
- long maxIdleTime = System.currentTimeMillis() - idleTimeout;
- if (idleStartTime <= maxIdleTime) {
- httpConnection.close();
- }
- }
將idleTimeout設為0可以確保鏈接被關閉。
以上這三種方法都是有客戶端主動關閉TCP鏈接的方法。下面再介紹由服務器端自動關閉鏈接的方法。
方法四:
代碼實現很簡單,所有代碼就和最上面的事例代碼一樣。只需要在HttpMethod method = new GetMethod("http://www.apache.org");加上一行HTTP頭的設置即可
- method.setRequestHeader("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
現在再說一下客戶端關閉鏈接和服務器端關閉鏈接的區別。如果采用客戶端關閉鏈接的方法,在客戶端的機器上使用netstat –an命令會看到很多TIME_WAIT的TCP鏈接。如果服務器端主動關閉鏈接這中情況就出現在服務器端。
參考WIKI上的說明http://wiki.apache.org/HttpComponents/FrequentlyAskedConnectionManagementQuestions
The TIME_WAIT state is a protection mechanism in TCP. The side that closes a socket connection orderly will keep the connection in state TIME_WAIT for some time, typically between 1 and 4 minutes.
TIME_WAIT的狀態會出現在主動關閉鏈接的這一端。TCP協議中TIME_WAIT狀態主要是為了保證數據的完整傳輸。具體可以參考此文檔:
http://www.softlab.ntua.gr/facilities/documentation/unix/unix-socket-faq/unix-socket-faq-2.html#ss2.7
另外強調一下使用上面這些方法關閉鏈接是在我們的應用中明確知道不需要重用鏈接時可以主動關閉鏈接來釋放資源。如果你的應用是需要重用鏈接的話就沒必要這么做,使用原有的鏈接還可以提供性能。
http://seanhe.iteye.com/blog/234759
可參看httpclient 3.x調優:http://hc.apache.org/httpclient-3.x/performance.html
- Reuse the HttpClient instance
- Connection persistence
- Concurrent execution of HTTP methods
- Request/Response entity streaming
- Expect-continue handshake
- Stale connection check
- Cookie processing
根據TCP協議,主動發起關閉的一方,會進入TIME_WAIT狀態,持續2*MSL(Max Segment Lifetime),缺省為240秒,在這個post中簡潔的介紹了為什么需要這個狀態。
值得一說的是,對於基於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,一個主要原因就是發現了這個問題。還有一個方法減緩TIME_WAIT壓力就是把系統的2*MSL時間減少,因為240秒的時間實在是忒長了點,對於Windows,修改注冊表,在HKEY_LOCAL_MACHINE\ SYSTEM\CurrentControlSet\Services\ Tcpip\Parameters上添加一個DWORD類型的值TcpTimedWaitDelay,一般認為不要少於60,不然可能會有麻煩。