httpclient的并发连接问题

本文探讨了在使用HTTP客户端进行并发请求时遇到的连接处理问题,通过分析httpclient的配置参数,发现默认设置可能导致的性能瓶颈,并提出了优化建议。通过调整maxHostConnections和maxTotalConnections参数,解决了重建索引速度减慢的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

昨天的搜索系统又出状况了,几个库同时重建索引变得死慢。经过一个上午的复现分析,确定问题出现httpclient的使用上(我使用的是3.1这个被广 泛使用的遗留版本)。搜索系统在重建索引时,是并发多个线程(默认是8个)不停的从PHP客户端取数据(当然,从另一个角度来说,搜索系统是客户 端,PHP端是服务端),取回后放到一个队列里由单独的一个或多个线程更新索引。在测试环境复现发现,对于一个请求,PHP端打印耗时是1-2秒,但搜索 端打印在4-6秒。这种耗时差别也就两种可能性,一个是PHP端返回到搜索端接受完耗时太长,另一个就是搜索端在真正发给PHP端数据前等待了很久。因为 有了之前的jetty7的困顿,起初我怀疑是传输数据的问题。因为请求数据的代码部分我只是简单的使用了httpclient,所以只能从 httpclient着手分析。我想到把PHP端和搜索端的请求起始和结束时间都打出来对照一下,不过在这样做之前我把搜索端的并发请求线程数调到了1, 看下单线程情况下效果如何,结果惊奇地发现PHP端和搜索端的耗时相近。所以,可以确定,是httpclient的并发连接处理上可能存在问题。不消说, 翻开httpclient API中和配置相关的接口,结果找到HttpConnectionManagerParams类中下面两个函数:

	public

 void

 setDefaultMaxConnectionsPerHost(

int

 maxHostConnections)

;


	public

 void

 setMaxTotalConnections(

int

 maxTotalConnections)

;

httpclient在处理请求连接方面使用了连接池,它内部实际上有两种连接池,一种是全局的ConnectionPool,一种是每主机(per- host)HostConnectionPool。参数maxHostConnections就HostConnectionPool中表示每主机可保持 连接的连接数,maxTotalConnections是ConnectionPool中可最多保持的连接数。每主机的配置类是 HostConfiguration,HttpClient有个int executeMethod(final HostConfiguration hostConfiguration, final HttpMethod method)方法可以指定使用哪个HostConfiguration,不过多数情况都是不显示指定HostConfiguration,这样 httpclient就用了默认的HostConfiguration=null,也就是说所有的请求可以认为指自同一个主机。如果不设置这两个参 数,httpclient自然会用默认的配置,也就是MultiThreadedHttpConnectionManager中的:

    /** The default maximum number of connections allowed per host */


    public

 static

 final

 int

 DEFAULT_MAX_HOST_CONNECTIONS =

 2

;

   // Per RFC 2616 sec 8.1.4


 
    /** The default maximum number of connections allowed overall */


    public

 static

 final

 int

 DEFAULT_MAX_TOTAL_CONNECTIONS =

 20

;

默认的maxHostConnections大小只有2,也就是说,在我并发8个线程请求数据时,实际上会有6个线程处于等待被调度,这也就解释上面的现 象了。再看看上面的注释,我从http://www.faqs.org/rfcs/rfc2616.html找到从了RFC 2616 sec 8.1.4 Practical Considerations段落的最后一段:

Clients that use persistent connections SHOULD limit the number of simultaneous connections that they maintain to a given server. A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy. A proxy SHOULD use up to 2*N connections to another server or proxy, where N is the number of simultaneously active users. These guidelines are intended to improve HTTP response times and avoid congestion.

看这叙述,也就表明人家httpclient设置maxHostConnections为2是有根有据的。不过,这种设置显然适合的是浏览器这种客户端, 但我相信,多数使用httpclient并不希望有这种默认的限制。而它默认的只有20的maxTotalConnections也太有些吝啬了。我后来 浏览solr的客户端server类CommonsHttpSolrServer的代码发现了下面的段落,solr要比我更了解httpclient:

    _httpClient =

 (

client ==

 null

)

 ?

 new

 HttpClient(

new

 MultiThreadedHttpConnectionManager(

)

)

 :

 client;


    if

 (

client ==

 null

)

 {


      // set some better defaults if we created a new connection manager and client


      // increase the default connections


      this

.setDefaultMaxConnectionsPerHost

(

 32

 )

;

  // 2


      this

.setMaxTotalConnections

(

 128

 )

;

 // 20


    }

对于httpclient,特别指出的是它的MultiThreadedHttpConnectionManager,看名字好像是多线程并发请求似的, 其实不是,但它也确实用到了多线程,那是在发现连接不够用时起个等待线程wait信号,这个名称的含义应该是多线程情况线程安全的 HttpConnectionManager。
使用httpclient还有两点经验,一个是创建的MultiThreadedHttpConnectionManager 实例最好是全局的,否则会有多个连接池,而HttpClient是线程安全的,可以多个实例。另一个是,在处理请求的最后,也就是finnaly里中,要 调用method.releaseConnection();回收连接,否则连接池就可能会爆了。

补充:写完之后倒在床上,我又想起了几个问题,这里补充上:
1、系统原先重建索引隐约记得速度还是可以的,为什么现在变慢得如此明显?这有两方面的原因,一个是原来系统取数据是用的单线程(我后来发现单取数据取数 据速度跟不上更新索引的速度所以改成多线程),另一个是,当时重建没有一下子同时开启数个库。所以,即便是同样的代码,环境变了,效果也可能变了。当这种 改变悄然发生了,程序员却没有捕捉到,才会第一直觉感到问题的诡异。
2、对于长时间不能获得连接的情况,httpclient是否有warn日志报出来?因为我使用了httpclient的 getResponseBodyAsStream方法,而它会打出warn日志,所以我是关掉了httpclient的warn级别的。所以,我又检查了 httpclient的代码,可惜没看到相关warning log,这点httpclient可以改进下。不过httpclient现在都是4时代了,而我使用的还是3.1的,而3.x已经被停止更新了,所以再采 用httpclient可以考虑4版本的,尽管现在能见到的代码几乎都是用的3.x系列的。
3、httpclient的文档是否有特别提到连接数配置的情况?我翻看了一下,确实在关于threading一页中有提到。不过,我等使用它时显然没有完整阅毕它的文档。

 

多线程编程

在 HttpClient 中使用多线程的一个主要原因是可以一次执行多个方法。在执行期间,每一个方法都使用一个 HttpConnection 实例。 由 于在同一时间多个连接只能安全地用于单一线程和方法和有限的资源,我们就必须确保连接分配给正确的方法。而 MultiThreadedHttpConnectionManager 完全可以代替我们完成这一项工作,这样我们就不必去考虑多线程带来安全的问题。

MultiThreadedHttpConnectionManager connectionManager =

                 new MultiThreadedHttpConnectionManager();

          HttpClient client = new HttpClient(connectionManager);

以上代码中的 HttpClient 就在多线程中执行多个方 法了。当我们再次调用 httpClient.executeMethod() 方法时,就会去 Connection Manager 中去请求 HttpConneciton 的实例,这样就避免了线程安全问题,因为 HttpClient 已经帮我们做了。

 

Options

 

MultThreadedHttpConnectionManager 参数配置:

 

connectionStaleCheckingEnabled :这个标志对所有已经创建的 connections 都适用。除特殊情况外,此值应该设置成 true 。

maxConnectionsPerHost :最大连 接数,默认是 2 。

maxTotalConnections :最大活动连 接数,默认是 20 。

 

释放连接

 

connection management 比较重要 的是当连接不再使用时,一定要手动释放。这样做的原因是 HttpClient 不能够确定哪个方法不被使用,哪个方法还在使用。这是因为 Response body 不是由 HttpClient 来自动读取其数据的,而是由使用 HttpClient 的应用程序来完成的。当读取 Response 的数据是时,必须使用此方法的连接。这样,在 Response 的数据在读取前, HttpClient 是没有释放连接的。所有这就要求 在读取完 Response 的数据后,应用程序及时的使用 releaseConnection ()方法来释放连接。

 

MultiThreadedHttpConnectionManager connectionManager =

                 new MultiThreadedHttpConnectionManager();

          HttpClient client = new HttpClient(connectionManager);

                     ...

        // and then from inside some thread executing a method

        GetMethod get = new GetMethod(“http://httpcomponents.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

            // manager

            get.releaseConnection();

        }

 

特别注意, 无论执行的方法或是否也不例外被抛出。对于每一个 HttpClient.executeMethod 方法必须有一个 method.releaseConnection ( )来释放连接。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值