結論:
如果使用httpclient 3.1並發量比較大的項目,最好升級到httpclient4.2.3上,保證並發量大時能抗住。httpclient 4.3.3,目前還有一些bug;還是用4.2.x穩定版本吧。
以庫存項目為例:
httpclient一天並發量在1500w左右,峰值一秒7萬。
在之前使用過程中,一直存在大量的
org.apache.http.conn.ConnectionPoolTimeoutException:
Timeout
waiting
for
connection
from
pool
at org.apache.http.impl.conn.PoolingClientConnectionManager.leaseConnection( PoolingClientConnectionManager.java: 232)
at org.apache.http.impl.conn.PoolingClientConnectionManager$ 1.getConnection( PoolingClientConnectionManager.java: 199)
at org.apache.http.impl.client.DefaultRequestDirector.execute( DefaultRequestDirector.java: 456)
at org.apache.http.impl.conn.PoolingClientConnectionManager.leaseConnection( PoolingClientConnectionManager.java: 232)
at org.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)
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)
問題:
因為使用了連接池,但連接不夠用,造成大量的等待;而且這種等待都有滾雪球的效應(和交易組最近使用的apache common dbcp存在的風險是類似的)。
解決方案
最終我們定了一些合理的參數值,目前來看還沒有遇到問題。
思考
其實出問題的原因是我們對一些參數不了解,隨意設置其值,不出現問題則好,出現問題很難排查到原因,因此我把使用httpclient必須設置的參數及代碼寫法及排查方法總結一下,供參考。
參數設置
1、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的區別:
1、MaxtTotal是整個池子的大小;
2、DefaultMaxPerRoute是根據連接到的主機對MaxTotal的一個細分;比如:
MaxtTotal=400 DefaultMaxPerRoute=200
而我只連接到http://www.baidu.com時,到這個主機的並發最多只有200;而不是400;
而我連接到http://www.baidu.com 和 http://qq.com時,到每個主機的並發最多只有200;即加起來是400(但不能超過400);所以起作用的設置是DefaultMaxPerRoute。
2、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、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();
}
2、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();也會關閉並釋放連接的
}
}
存在的風險
1、httpclient 4.2.3 在釋放連接時
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);
}
}
無風險
2、httpclient 3.1
1、如果走http1.1協議:如果proxy-connection/connection請求頭設置為close;那么會關閉socket; 或者這兩個頭不等於close 也會自動關;
2、如果是keep-alive ,不會關閉;
3、如果協議小於等於http1.0協議沒有問題;調用releaseConnection時會close socket;
4、其他情況不會close;
也就是說如果走http1.1且沒有設置相關參數;那么socket其實是沒有關閉的;可能造成很多TIME_WAIT;因此如果是走短連接建議設置postMethod.setParameter("Connection", "close")。
其他注意事項:
1、使用keep-alive一定要設置Content-Length頭(否則也不是長連接)。
2、在使用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);
3、鎖
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
轉自:http://jinnianshilongnian.iteye.com/blog/2089792
