使用httpclient必须知道的参数设置及代码写法、存在的风险
结论:
如果使用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)
另外通过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)
问题:
因为使用了连接池,但连接不够用,造成大量的等待;而且这种等待都有滚雪球的效应(和交易组最近使用的apache common dbcp存在的风险是类似的)。
解决方案
最终我们定了一些合理的参数值,目前来看还没有遇到问题。
思考
其实出问题的原因是我们对一些参数不了解,随意设置其值,不出现问题则好,出现问题很难排查到原因,因此我把使用httpclient必须设置的参数及代码写法及排查方法总结一下,供参考。
参数设置
1、httpclient 4.2.3
此处解释下MaxtTotal和DefaultMaxPerRoute的区别:
1、MaxtTotal是整个池子的大小;
2、DefaultMaxPerRoute是根据连接到的主机对MaxTotal的一个细分;比如:
MaxtTotal=400 DefaultMaxPerRoute=200
而我只连接到http://sishuok.com时,到这个主机的并发最多只有200;而不是400;
而我连接到http://sishuok.com 和 http://qq.com时,到每个主机的并发最多只有200;即加起来是400(但不能超过400);所以起作用的设置是DefaultMaxPerRoute。
2、httpclient 3.1
参数类似 就不多解释了;
代码写法
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
也就是说如果走http1.1且没有设置相关参数;那么socket其实是没有关闭的;可能造成很多TIME_WAIT;因此如果是走短连接建议设置postMethod.setParameter(“Connection”, “close”)。
其他注意事项:
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
这也是为什么在库存项目中使用httpclient 3.1 依然有大量的wait,而httpclient4.2.3 一个没有的问题所在。
如有问题,请赐教。