0
点赞
收藏
分享

微信扫一扫

使用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

HttpParams params = new BasicHttpParams(); 
   
 
   

     //设置连接超时时间 
   
 
   
//设置请求超时2秒钟 根据业务调整
 
   
//设置等待数据超时时间2秒钟 根据业务调整
 
   
//定义了当从ClientConnectionManager中检索ManagedClientConnection实例时使用的毫秒级的超时时间
 
   
//这个参数期望得到一个java.lang.Long类型的值。如果这个参数没有被设置,默认等于CONNECTION_TIMEOUT,因此一定要设置
 
   
//该值就是连接不够用的时候等待超时时间,一定要设置,而且不能太大 ()
 
   

       
   
 
   

     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(); 
   
 
   
 //设置整个连接池最大连接数 根据自己的场景决定
 
   

     //是路由的默认最大连接(该值默认为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://sishuok.com时,到这个主机的并发最多只有200;而不是400;



而我连接到http://sishuok.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



2.621 seconds



1.983 seconds



 

举报

相关推荐

0 条评论