在多线程环境中使用HttpClient的最佳实践

96

我在多线程环境下一直都在使用HttpClient。对于每个线程,当它初始化一个连接时,都会创建一个全新的HttpClient实例。

最近,我发现这种方法可能会导致用户打开过多的端口,并且大部分连接处于TIME_WAIT状态。

http://www.opensubscriber.com/message/commons-httpclient-dev@jakarta.apache.org/86045.html

因此,不是每个线程都进行以下操作:

HttpClient c = new HttpClient();
try {
    c.executeMethod(method);
}
catch(...) {
}
finally {
    method.releaseConnection();
}
我们计划拥有:

[方法A]

// global_c is initialized once through
// HttpClient global_c = new HttpClient(new MultiThreadedHttpConnectionManager());

try {
    global_c.executeMethod(method);
}
catch(...) {
}
finally {
    method.releaseConnection();
}
在正常情况下,global_c将被50多个线程同时访问。我想知道这是否会导致性能问题?MultiThreadedHttpConnectionManager是否使用无锁机制实现其线程安全策略?
如果10个线程正在使用global_c,那其他40个线程会被锁定吗?
还是说在每个线程中创建一个HttpClient实例,但显式释放连接管理器会更好?[方法B]
MultiThreadedHttpConnectionManager connman = new MultiThreadedHttpConnectionManager();
HttpClient c = new HttpClient(connman);
try {
      c.executeMethod(method);
}
catch(...) {
}
finally {
    method.releaseConnection();
    connman.shutdown();
}

connman.shutdown() 会受到性能问题的影响吗?

我想知道对于使用50多个线程的应用程序,哪种方法(A或B)更好?

5个回答

49

绝对是方法A,因为它是池化的并且线程安全的。

如果您正在使用httpclient 4.x,则连接管理器称为ThreadSafeClientConnManager。请参阅link以获取更多详细信息(向下滚动至“池化连接管理器”)。例如:

    HttpParams params = new BasicHttpParams();
    SchemeRegistry registry = new SchemeRegistry();
    registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
    ClientConnectionManager cm = new ThreadSafeClientConnManager(params, registry);
    HttpClient client = new DefaultHttpClient(cm, params);

52
ThreadSafeClientConnManager已经被废弃,推荐使用PoolingClientConnManager(4.2版本及以上)。 - Drew Stephens
你好,通过这种方式创建的httpclient能否像这里(https://dev59.com/JW025IYBdhLWcg3whGWx ...)所描述的那样用于维持会话?因为我尝试过,但无法在不同请求之间维护会话... - sakthig
19
4.3.1这里:PoolingClientConnManager已经被弃用,建议使用PoolingHttpClientConnectionManager。 - Matthias
1
@DrewStephens 再次强调,PoolingClientConnManager已被弃用,建议使用PoolingHttpClientConnectionManager。 - didxga
1
@didxga - 我还在等待PoolingHttpClientConnectionManager被弃用,然后才开始我的项目。 - bit_cracker007

18

1
如果客户端被设置为全局,何时会调用连接管理器上的“shutdown”方法? - Wand Maker
1
哪些工具/ Linux 命令有助于调试或“可视化” ConnectionManager 的行为?我问这个问题是因为我们目前在 CLOSE_WAIT 和其他状况下存在连接问题,我们正在苦苦寻找一种好的方式来查看到底发生了什么。 - Christoph
@WandMaker 我相信当任一程序退出或者你完成某个工作批次且在一段时间内不需要任何连接时,你只需调用shutdown。 - Nicholas DiPiazza
1
@Christoph netstat 在这方面做得非常好。http://technet.microsoft.com/en-us/sysinternals/bb897437.aspx - Nicholas DiPiazza

13

根据文档,我的理解是HttpConnection本身并不被视为线程安全的,因此MultiThreadedHttpConnectionManager提供了可重用的HttpConnection池。你只需要一个单独的MultiThreadedHttpConnectionManager与所有线程共享,并且只初始化一次。所以你需要对选项A进行一些小的改进。

MultiThreadedHttpConnectionManager connman = new MultiThreadedHttpConnectionManag

每个线程都应该针对每个请求使用序列,从连接池获取连接,并在完成工作后将其放回 - 使用finally块可能是一个好的选择。您还应该为连接池没有可用连接的情况进行编码并处理超时异常。

HttpConnection connection = null
try {
    connection = connman.getConnectionWithTimeout(
                        HostConfiguration hostConfiguration, long timeout) 
    // work
} catch (/*etc*/) {/*etc*/} finally{
    if ( connection != null )
        connman.releaseConnection(connection);
}

由于您正在使用连接池,因此您实际上不会关闭连接,因此这不应该引起TIME_WAIT问题。这种方法假定每个线程不会长时间保持连接。请注意,conman本身保持打开状态。


没有实际回答我的问题,哪种方法(A还是B)更好。 - Cheok Yan Cheng

7

使用HttpClient 4.5,您可以这样做:

CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(new PoolingHttpClientConnectionManager()).build();

注意,这个实现了 Closeable 接口(用于关闭连接管理器)。

5

1
糟糕。没有计划从HttpClient 3.x迁移到4.x,因为3.x在我的应用中已经稳定运行了将近2年 :) - Cheok Yan Cheng
9
没问题,只是如果有其他人在谷歌搜索答案的话 :) - Thomas Ahle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接