使用httpclient必須知道的參數設置及代碼寫法、存在的風險

原文出處: 張開濤html

結論:

若是使用httpclient 3.1併發量比較大的項目,最好升級到httpclient4.2.3上,保證併發量大時能抗住。httpclient 4.3.3,目前還有一些bug;仍是用4.2.x穩定版本吧。java

以庫存項目爲例:

httpclient一天併發量在1500w左右,峯值一秒7萬。apache

在以前使用過程當中,一直存在大量的編程

org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
atorg.apache.http.impl.conn.PoolingClientConnectionManager.leaseConnection(PoolingClientConnectionManager.java:232)
atorg.apache.http.impl.conn.PoolingClientConnectionManager$1.getConnection(PoolingClientConnectionManager.java:199)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:456)架構

另外經過jstack查看線程,會發現:併發

「pool-21-thread-3″ prio=10 tid=0x00007f6b7c002800 nid=0x40ff waiting on condition [0x00007f6b37020000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000f97918b8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkUntil(LockSupport.java:239)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitUntil(AbstractQueuedSynchronizer.java:2072)
at org.apache.http.pool.PoolEntryFuture.await(PoolEntryFuture.java:129)
at org.apache.http.pool.AbstractConnPool.getPoolEntryBlocking(AbstractConnPool.java:281)
at org.apache.http.pool.AbstractConnPool.access$000(AbstractConnPool.java:62)
at org.apache.http.pool.AbstractConnPool$2.getPoolEntry(AbstractConnPool.java:176)
at org.apache.http.pool.AbstractConnPool$2.getPoolEntry(AbstractConnPool.java:172)
at org.apache.http.pool.PoolEntryFuture.get(PoolEntryFuture.java:100)
at org.apache.http.impl.conn.PoolingClientConnectionManager.leaseConnection(PoolingClientConnectionManager.java:212)socket

問題:

由於使用了鏈接池,但鏈接不夠用,形成大量的等待;並且這種等待都有滾雪球的效應(和交易組最近使用的apache common dbcp存在的風險是相似的)。post

解決方案

最終咱們定了一些合理的參數值,目前來看尚未遇到問題。測試

思考

其實出問題的緣由是咱們對一些參數不瞭解,隨意設置其值,不出現問題則好,出現問題很難排查到緣由,所以我把使用httpclient必須設置的參數及代碼寫法及排查方法總結一下,供參考。網站

參數設置

一、httpclient 4.2.3

HttpParams params = new BasicHttpParams();

//設置鏈接超時時間

Integer CONNECTION_TIMEOUT = 2 * 1000; //設置請求超時2秒鐘 根據業務調整

Integer SO_TIMEOUT = 2 * 1000; //設置等待數據超時時間2秒鐘 根據業務調整

//定義了當從ClientConnectionManager中檢索ManagedClientConnection實例時使用的毫秒級的超時時間

//這個參數指望獲得一個java.lang.Long類型的值。若是這個參數沒有被設置,默認等於CONNECTION_TIMEOUT,所以必定要設置

Long CONN_MANAGER_TIMEOUT = 500L; //該值就是鏈接不夠用的時候等待超時時間,必定要設置,並且不能太大 ()

params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, CONNECTION_TIMEOUT);

params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, SO_TIMEOUT);

params.setLongParameter(ClientPNames.CONN_MANAGER_TIMEOUT, CONN_MANAGER_TIMEOUT);

//在提交請求以前 測試鏈接是否可用

params.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, true);

PoolingClientConnectionManager conMgr = new PoolingClientConnectionManager();

conMgr.setMaxTotal(200); //設置整個鏈接池最大鏈接數 根據本身的場景決定

//是路由的默認最大鏈接(該值默認爲2),限制數量實際使用DefaultMaxPerRoute並不是MaxTotal。

//設置太小沒法支持大併發(ConnectionPoolTimeoutException: Timeout waiting for connection from pool),路由是對maxTotal的細分。

conMgr.setDefaultMaxPerRoute(conMgr.getMaxTotal());//(目前只有一個路由,所以讓他等於最大值)

//另外設置http client的重試次數,默認是3次;當前是禁用掉(若是項目量不到,這個默認便可)

httpClient.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0, false));

此處解釋下MaxtTotal和DefaultMaxPerRoute的區別:

一、MaxtTotal是整個池子的大小;

二、DefaultMaxPerRoute是根據鏈接到的主機對MaxTotal的一個細分;好比:

MaxtTotal=400 DefaultMaxPerRoute=200

而我只鏈接到http://sishuok.com時,到這個主機的併發最多隻有200;而不是400;

而我鏈接到http://sishuok.com 和 http://qq.com時,到每一個主機的併發最多隻有200;即加起來是400(但不能超過400);因此起做用的設置是DefaultMaxPerRoute。

二、httpclient 3.1

HttpConnectionManagerParams params = new HttpConnectionManagerParams();

params.setConnectionTimeout(2000);

params.setSoTimeout(2000);

// 最大鏈接數

params.setMaxTotalConnections(500);

params.setDefaultMaxConnectionsPerHost(500);

params.setStaleCheckingEnabled(true);

connectionManager.setParams(params);

HttpClientParams httpClientParams = new HttpClientParams();

// 設置httpClient的鏈接超時,對鏈接管理器設置的鏈接超時是無用的

httpClientParams.setConnectionManagerTimeout(5000); //等價於4.2.3中的CONN_MANAGER_TIMEOUT

httpClient = new HttpClient(connectionManager);

httpClient.setParams(httpClientParams);

//另外設置http client的重試次數,默認是3次;當前是禁用掉(若是項目量不到,這個默認便可)

httpClientParams.setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(0, false));

參數相似 就很少解釋了;

代碼寫法

一、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
httpclient 4.2 . 3
HttpResponse response = null ;
HttpEntity entity = null ;
try {
   HttpGet get = new HttpGet();
   String url = "http://hc.apache.org/" ;
   get.setURI( new URI(url));
   response = getHttpClient().execute(get);
/  /處理響應
} catch (Exception e) {
   //處理異常
} finally {
   if (response != null ) {
     EntityUtils.consume(response.getEntity()); //會自動釋放鏈接
   }
   //以下方法也是能夠的,可是存在一些風險;不要用
   //InputStream is = response.getEntity().getContent();
   //is.close();
}

二、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
httpclient 3.1
PostMethod postMethod = new PostMethod(yxUrl);
try {
   httpClient.executeMethod(postMethod);
} catch (Exception e) {
   //處理異常
} finally {
   if (postMethod != null ) { //不要忘記釋放,儘可能經過該方法實現,
     postMethod.releaseConnection();
     //存在風險,不要用
     //postMethod.setParameter("Connection", "close");
     //InputStream is = postMethod.getResponseBodyAsStream();
     //is.clsoe();也會關閉並釋放鏈接的
   }
}

存在的風險

一、httpclient 4.2.3 在釋放鏈接時

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if (managedConn.isOpen() && !managedConn.isMarkedReusable()) { //若是鏈接打開的且不可重用(not keepalive) close socket
   try {
     managedConn.shutdown();
   } catch (IOException iox) {
     if ( this .log.isDebugEnabled()) {
       this .log.debug( "I/O exception shutting down released connection" , iox);
     }
   }
}
// Only reusable connections can be kept alive
if (managedConn.isMarkedReusable()) {
   entry.updateExpiry(keepalive, tunit != null ? tunit : TimeUnit.MILLISECONDS);
   if ( this .log.isDebugEnabled()) {
     String s;
     if (keepalive > 0 ) {
       s = "for " + keepalive + " " + tunit;
     } else {
       s = "indefinitely" ;
     }
     this .log.debug( "Connection " + format(entry) + " can be kept alive " + s);
   }
}

無風險

二、httpclient 3.1

一、若是走http1.1協議:若是proxy-connection/connection請求頭設置爲close;那麼會關閉socket; 或者這兩個頭不等於close 也會自動關;

二、若是是keep-alive ,不會關閉;

三、若是協議小於等於http1.0協議沒有問題;調用releaseConnection時會close socket;

四、其餘狀況不會close;

也就是說若是走http1.1且沒有設置相關參數;那麼socket實際上是沒有關閉的;可能形成不少TIME_WAIT;所以若是是走短鏈接建議設置postMethod.setParameter(「Connection」, 「close」)。

其餘注意事項:

一、使用keep-alive必定要設置Content-Length頭(不然也不是長鏈接)。

二、在使用httpclient3.1時(4.2.3沒問題);儘可能不要調用 byte[] getResponseBody() :由於若是Content-Length沒設置或者傳輸的數據大於1M,會有大量以下日誌

LOG.warn(「Going to buffer response body of large or unknown size. 「

+」Using getResponseBodyAsStream instead is recommended.」);

若是大於1M能夠設置該參數;可是-1的話就沒辦法了,就不要調用 byte[] getResponseBody()

httpClientParams.setLongParameter(HttpMethodParams.BUFFER_WARN_TRIGGER_LIMIT, 2L * 1024 * 1024);

三、鎖

httpclient 3.1 使用synchronized+wait+notifyAll,存在兩個問題,量大synchronized慢和notifyAll可能形成線程飢餓;httpclient 4.2.3 使用 ReentrantLock(默認非公平) + Condition(每一個線程一個)。

這裏有個測試:http://java.dzone.com/articles/synchronized-vs-lock ,在我本機(jdk1.6.0_43 )測試結果明細鎖的優點比較大

1x synchronized {} with 32 threads took 2.621 seconds

1x Lock.lock()/unlock() with 32 threads took 1.951 seconds

1x AtomicInteger with 32 threads took 4.113 seconds

1x synchronized {} with 64 threads took 2.621 seconds

1x Lock.lock()/unlock() with 64 threads took 1.983 seconds

這也是爲何在庫存項目中使用httpclient 3.1 依然有大量的wait,而httpclient4.2.3 一個沒有的問題所在。

http://cxy.liuzhihengseo.com/471.html

問啊-定製化IT教育平臺,牛人一對一服務,有問必答,開發編程社交頭條 官方網站:www.wenaaa.com 下載問啊APP,參與官方懸賞,賺百元現金。QQ羣290551701 彙集不少互聯網精英,技術總監,架構師,項目經理!開源技術研究,歡迎業內人士,大牛及新手有志於從事IT行業人員進入!

相關文章
相關標籤/搜索