轉載請註明出處,謝謝~
http://blog.csdn.net/shootyou/archive/2011/05/12/6415248.aspx
在一次服務器異常的排查過程當中(服務器異常排查的過程我會另起文章),我們決定使用HttpClient4.X替代HttpClient3.X或者HttpConnection。
爲什麼使用HttpClient4?主要是HttpConnection沒有連接池的概念,多少次請求就會建立多少個IO,在訪問量巨大的情況下服務器的IO可能會耗盡。
HttpClient3也有連接池的東西在裏頭,使用MultiThreadedHttpConnectionManager,大致過程如下:
- MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
- HttpClient client = new HttpClient(connectionManager);...// 在某個線程中。
- GetMethod get = new GetMethod("http://jakarta.apache.org/");
- try {
- client.executeMethod(get);// print response to stdout
- System.out.println(get.getResponseBodyAsStream());
- } finally {
- // be sure the connection is released back to the connection
- managerget.releaseConnection();
- }
可以看出來,它的方式與jdbc連接池的使用方式相近,我覺得比較不爽的就是需要手動調用releaseConnection去釋放連接。對每一個HttpClient.executeMethod須有一個method.releaseConnection()與之匹配。
HttpClient4在這點上做了改進,使用我們常用的InputStream.close()來確認連接關閉(4.1版本之前使用entity.consumeContent()來確認內容已經被消耗關閉連接)。具體方式如下:
- ...HttpClient client = null;InputStream in = null;
- try{
- client = HttpConnectionManager.getHttpClient();
- HttpGet get = new HttpGet();
- get.setURI(new URI(urlPath));
- HttpResponse response = client.execute(get);
- HttpEntity entity =response.getEntity();
- if( entity != null ){
- in = entity.getContent();
- ....
- }catch (Exception e){
- ....
- }finally{
- if (in != null){
- try{in.close ();}catch (IOException e){
- e.printStackTrace ();
- }
- }
- }
2012-03-06更新:
有網友提出調用in.close()是否會關閉底層socket,事情是這樣的:
- 回覆kangkang203:感謝你提出的這個問題。
- 首先我文中提出的方法in.close()它會觸發一個連接的釋放這個連接將重新被連接管理器收回,官網的原文是這麼說的:“Closing the input stream will trigger connection release...the underlying connection gets released back to the connection manager”。但是底層的socket是否會被關閉是不一定的,我看了部分源碼(EofSensorInputStream)發現,大多數情況socket並不會關閉,而是否關閉socket貌似是由一個Watcher去決定的。所以in.close的調用不會引起socket的關閉。
- 另外,由於http本身我們把它當做“短連接”,所以在一次請求交互完成後仍然打開socket的意義不是很大,畢竟它不像長連接那樣在一個連接建立之後會有很多次數據交互。我們試用連接管理器的更多意義在於它對連接的管理。
好說完了連接池的使用流程,現在來說一說連接池在使用時最重要的幾個參數。我用4.1的版本實現了一個簡單的HttpConnectionManager,代碼如下:
- public class HttpConnectionManager {
- private static HttpParams httpParams;
- private static ClientConnectionManager connectionManager;
- /**
- * 最大連接數
- */
- public final static int MAX_TOTAL_CONNECTIONS = 800;
- /**
- * 獲取連接的最大等待時間
- */
- public final static int WAIT_TIMEOUT = 60000;
- /**
- * 每個路由最大連接數
- */
- public final static int MAX_ROUTE_CONNECTIONS = 400;
- /**
- * 連接超時時間
- */
- public final static int CONNECT_TIMEOUT = 10000;
- /**
- * 讀取超時時間
- */
- public final static int READ_TIMEOUT = 10000;
- static {
- httpParams = new BasicHttpParams();
- // 設置最大連接數
- ConnManagerParams.setMaxTotalConnections(httpParams, MAX_TOTAL_CONNECTIONS);
- // 設置獲取連接的最大等待時間
- ConnManagerParams.setTimeout(httpParams, WAIT_TIMEOUT);
- // 設置每個路由最大連接數
- ConnPerRouteBean connPerRoute = new ConnPerRouteBean(MAX_ROUTE_CONNECTIONS);
- ConnManagerParams.setMaxConnectionsPerRoute(httpParams,connPerRoute);
- // 設置連接超時時間
- HttpConnectionParams.setConnectionTimeout(httpParams, CONNECT_TIMEOUT);
- // 設置讀取超時時間
- HttpConnectionParams.setSoTimeout(httpParams, READ_TIMEOUT);
- SchemeRegistry registry = new SchemeRegistry();
- registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
- registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
- connectionManager = new ThreadSafeClientConnManager(httpParams, registry);
- }
- public static HttpClient getHttpClient() {
- return new DefaultHttpClient(connectionManager, httpParams);
- }
- }
最大連接數、獲取連接的最大等待時間、讀取超時時間 這些配置應該比較容易理解,一般的連接池都會有這些配置,比較特別的是 每個路由(route)最大連接數 。
什麼是一個route?
這裏route的概念可以理解爲 運行環境機器 到 目標機器的一條線路。舉例來說,我們使用HttpClient的實現來分別請求 www.baidu.com 的資源和 www.bing.com 的資源那麼他就會產生兩個route。
這裏爲什麼要特別提到route最大連接數這個參數呢,因爲這個參數的默認值爲2,如果不設置這個參數值默認情況下對於同一個目標機器的最大併發連接只有2個!這意味着如果你正在執行一個針對某一臺目標機器的抓取任務的時候,哪怕你設置連接池的最大連接數爲200,但是實際上還是隻有2個連接在工作,其他剩餘的198個連接都在等待,都是爲別的目標機器服務的。
怎麼樣蛋疼吧,我是已經有過血的教訓了,在切換到HttpClient4.1的起初沒有注意到這個配置,最後使得服務承受的壓力反而不如從前了,所以在這裏特別提醒大家注意。
HttpClient4.X 教程下載:
關於版本的補充:
網友w2449008821提醒之後我才發現在HttpClient4.1+的版本ConnManagerParams已經被Deprecated了。
我在寫這篇日誌的時候時候的httpclient 版本是4.0.3,從4.0版本之後ConnManagerParams被Deprecated,沒想到一個小版本升級會有這麼大變化。
官網教程舉例了新的連接池設置:
- SchemeRegistry schemeRegistry = new SchemeRegistry();
- schemeRegistry.register(
- new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
- schemeRegistry.register(
- new Scheme("https", 443, SSLSocketFactory.getSocketFactory()));
- ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(schemeRegistry);
- // Increase max total connection to 200
- cm.setMaxTotalConnections(200);
- // Increase default max connection per route to 20
- cm.setDefaultMaxPerRoute(20);
- // Increase max connections for localhost:80 to 50
- HttpHost localhost = new HttpHost("locahost", 80);
- cm.setMaxForRoute(new HttpRoute(localhost), 50);
- HttpClient httpClient = new DefaultHttpClient(cm);
static ConnPerRoute |
getMaxConnectionsPerRoute(HttpParams params) Deprecated. use ThreadSafeClientConnManager.getMaxForRoute(org.apache.http.conn.routing.HttpRoute) |
static int |
getMaxTotalConnections(HttpParams params) Deprecated. use ThreadSafeClientConnManager.getMaxTotal() |
static long |
getTimeout(HttpParams params) Deprecated. use HttpConnectionParams.getConnectionTimeout(HttpParams) |
static void |
setMaxConnectionsPerRoute(HttpParams params, ConnPerRoute connPerRoute) Deprecated. use ThreadSafeClientConnManager.setMaxForRoute(org.apache.http.conn.routing.HttpRoute,
int) |
static void |
setMaxTotalConnections(HttpParams params,
int maxTotalConnections) Deprecated. use ThreadSafeClientConnManager.setMaxTotal(int) |
static void |
setTimeout(HttpParams params,
long timeout) Deprecated. use HttpConnectionParams.setConnectionTimeout(HttpParams,
int) |
http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e638