性能:Apache HttpAsyncClient与多线程URLConnection

时间:2018-12-07 06:31:37

标签: java performance http asynchronous apache-httpasyncclient

我正在尝试选择最佳方法来并行进行大量http请求。以下是我到目前为止拥有的两种方法:

  1. 使用Apache HttpAsyncClient和CompletableFutures:

    try (CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom()
    .setMaxConnPerRoute(2000).setMaxConnTotal(2000)
    .setUserAgent("Mozilla/4.0")
    .build()) {
    httpclient.start();
    HttpGet request = new HttpGet("http://bing.com/");
    long start = System.currentTimeMillis();
    CompletableFuture.allOf(
            Stream.generate(()->request).limit(1000).map(req -> {
                CompletableFuture<Void> future = new CompletableFuture<>();
                httpclient.execute(req, new FutureCallback<HttpResponse>() {
                    @Override
                    public void completed(final HttpResponse response) {
                        System.out.println("Completed with: " + response.getStatusLine().getStatusCode())
                        future.complete(null);
                    }
                    ...
                });
                System.out.println("Started request");
                return future;
    }).toArray(CompletableFuture[]::new)).get();
    
  2. 传统的“每请求线程数”方法:

    long start1 = System.currentTimeMillis();
    URL url = new URL("http://bing.com/");
    ExecutorService executor = Executors.newCachedThreadPool();
    
    Stream.generate(()->url).limit(1000).forEach(requestUrl ->{
        executor.submit(()->{
            try {
                URLConnection conn = requestUrl.openConnection();
                System.out.println("Completed with: " + conn.getResponseCode());
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        System.out.println("Started request");
    });
    

在多次运行中,我注意到传统方法的完成速度几乎是异步/未来方法的两倍

尽管我期望专用线程能够运行得更快,但是差异应该是这么显着还是异步实现有问题?如果没有,那么正确的方法是什么?

1 个答案:

答案 0 :(得分:3)

存在的问题取决于很多因素:

  • 硬件
  • 操作系统(及其配置)
  • JVM实现
  • 网络设备
  • 服务器行为

第一个问题-差异应该这么大吗?

取决于负载,池大小和网络,但是在每个方向上它可能远远超过观察到的2倍(有利于异步或线程解决方案)。根据您稍后的评论,差异更多是因为行为不当,但为了争辩,我将解释可能的情况。

专用线程可能是一个负担。 (如果委派了这些任务,则在使用Oracle [HotSpot] JVM的情况下,中断处理和线程调度将由操作系统完成。)如果线程过多,则OS /系统可能会变得无响应,从而减慢了批处理的速度(或其他任务)。关于线程管理,有许多管理任务,这就是为什么线程(和连接)池成为问题。尽管一个好的操作系统应该能够处理数千个并发线程,但是总会有一些限制或(内核)事件发生的机会。

这是池和异步行为派上用场的地方。例如,有一个由10个物理线程组成的池来完成所有工作。如果某些内容被阻止(在这种情况下等待服务器响应),则它会进入“已阻止”状态(参见图片),并且以下任务将使物理线程完成某些工作。通知线程(数据到达)后,该线程将变为“可运行”(从那时起,池化机制可以将其拾取[这可能是OS或JVM实现的解决方案])。为了进一步阅读线程状态,我建议使用W3Rescue。为了更好地理解线程池,我建议使用此baeldung article

Thread transitions

第二个问题-异步实现有问题吗?如果没有,那么正确的方法是什么?

实现正常,没有问题。行为与线程方式不同。这些情况下的主要问题主要是什么是SLA(服务水平协议)。如果您是服务的唯一“客户,那么基本上您必须在latency or throughput之间做出选择,但是该决定只会影响您自己。通常情况并非如此,因此我建议您使用某种支持的池

第三个问题-但是,我刚刚指出,您以字符串形式读取响应流的时间大致相同。我想知道为什么会这样吗?

在两种情况下,消息很可能完全到达了(可能响应不是只是几个http包的流),但是如果您仅读取标头,则不需要解析响应并将其本身加载到CPU寄存器,从而减少了读取实际接收到的数据的等待时间。我认为这是等待时间(sourcesource)的不错表现: Reach times

这是一个很长的答案,所以TL.DR。 是一个真正的核心话题,它取决于很多事情:

  • 硬件:物理核心数,多线程容量,内存速度,网络接口
  • 操作系统(及其配置):线程管理,中断处理
  • JVM实现:线程管理(内部或外包到OS),更不用说GC和JIT配置了
  • 网络设备:一些限制来自给定IP的并发连接,一些合并非HTTPS连接并充当代理
  • 服务器行为:集中的工作者或按请求的工作者等

在您的情况下,服务器很可能是瓶颈,因为在更正的情况下,两种方法给出的结果相同(HttpResponse::getStatusLine().getStatusCode() and HttpURLConnection::getResponseCode())。为了给出正确的答案,您应该使用JMeterLoadRunner等工具评估服务器性能,然后相应地确定解决方案的大小。 This article是有关数据库连接池的更多内容,但该逻辑也适用于此。