Err*_*ric 5 java performance asynchronous http apache-httpasyncclient
我正在尝试选择最好的方法来并行进行大量的http请求。以下是我到目前为止拥有的两种方法:
使用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();
Run Code Online (Sandbox Code Playgroud)常规的每请求线程数方法:
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");
});
Run Code Online (Sandbox Code Playgroud)在多次运行中,我注意到传统方法的完成速度几乎是异步/未来方法的两倍。
尽管我期望专用线程能够运行得更快,但是差异应该是这么明显还是异步实现有问题?如果没有,那么正确的方法是什么?
这个问题取决于很多因素:
第一个问题 - 差异应该如此显着吗?
取决于负载、池大小和网络,但在每个方向上它可能比观察到的因子 2 多得多(有利于异步或线程解决方案)。根据您后来的评论,差异更多是因为不当行为,但为了论证起见,我将解释可能的情况。
专用线程可能是一个相当大的负担。(中断处理和线程调度由操作系统完成,以防您使用 Oracle [HotSpot] JVM,因为这些任务是委托的。)如果线程过多,操作系统/系统可能会变得无响应,从而减慢您的批处理速度(或其他任务)。有很多关于线程管理的管理任务,这就是为什么线程(和连接)池是一个东西。虽然一个好的操作系统应该能够处理几千个并发线程,但总是有可能发生一些限制或(内核)事件。
这是池化和异步行为派上用场的地方。例如,有 10 个物理线程池完成所有工作。如果某些东西被阻塞(在这种情况下等待服务器响应),它会进入“阻塞”状态(参见图片)并且以下任务让物理线程做一些工作。当一个线程被通知(数据到达)时,它变成“可运行的”(从这一点池机制能够拿起它[这可能是操作系统或 JVM 实现的解决方案])。为了进一步阅读线程状态,我推荐W3Rescue。为了更好地理解线程池,我推荐这篇baeldung 文章。
第二个问题 - 异步实现有问题吗?如果不是,那么在这里进行的正确方法是什么?
实现是可以的,没有问题。行为与线程方式不同。这些情况下的主要问题主要是 SLA-s(服务级别协议)是什么。如果您是该服务的唯一“客户”,那么基本上您必须在延迟或吞吐量之间做出决定,但该决定只会影响您。大多数情况下并非如此,因此我建议使用某种受支持的池化您正在使用的库。
第三个问题 - 但是我刚刚注意到,当您将响应流作为字符串读取时,所花费的时间大致相同。我想知道这是为什么?
在这两种情况下,消息最有可能完全到达(可能响应不是流,只是几个 http 包),但是如果您只读取不需要响应本身被解析并加载到 CPU 寄存器的标头,从而减少读取接收到的实际数据的延迟。我认为这是一个很酷的延迟表示(源和源):

这是一个很长的答案,所以 TL.DR.:缩放是一个非常核心的话题,它取决于很多事情:
HTTPS连接并充当代理在您的情况下,服务器很可能是瓶颈,因为两种方法在更正的情况下给出了相同的结果 ( HttpResponse::getStatusLine().getStatusCode() and HttpURLConnection::getResponseCode())。为了给出正确的答案,您应该使用一些工具(如JMeter或LoadRunner等)来衡量您的服务器性能,然后相应地调整您的解决方案。这篇文章更多是关于数据库连接池,但逻辑也适用于这里。
| 归档时间: |
|
| 查看次数: |
1008 次 |
| 最近记录: |