我正在尝试选择最佳方法来并行进行大量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();
传统的“每请求线程数”方法:
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");
});
在多次运行中,我注意到传统方法的完成速度几乎是异步/未来方法的两倍。
尽管我期望专用线程能够运行得更快,但是差异应该是这么显着还是异步实现有问题?如果没有,那么正确的方法是什么?
答案 0 :(得分:3)
存在的问题取决于很多因素:
第一个问题-差异应该这么大吗?
取决于负载,池大小和网络,但是在每个方向上它可能远远超过观察到的2倍(有利于异步或线程解决方案)。根据您稍后的评论,差异更多是因为行为不当,但为了争辩,我将解释可能的情况。
专用线程可能是一个负担。 (如果委派了这些任务,则在使用Oracle [HotSpot] JVM的情况下,中断处理和线程调度将由操作系统完成。)如果线程过多,则OS /系统可能会变得无响应,从而减慢了批处理的速度(或其他任务)。关于线程管理,有许多管理任务,这就是为什么线程(和连接)池成为问题。尽管一个好的操作系统应该能够处理数千个并发线程,但是总会有一些限制或(内核)事件发生的机会。
这是池和异步行为派上用场的地方。例如,有一个由10个物理线程组成的池来完成所有工作。如果某些内容被阻止(在这种情况下等待服务器响应),则它会进入“已阻止”状态(参见图片),并且以下任务将使物理线程完成某些工作。通知线程(数据到达)后,该线程将变为“可运行”(从那时起,池化机制可以将其拾取[这可能是OS或JVM实现的解决方案])。为了进一步阅读线程状态,我建议使用W3Rescue。为了更好地理解线程池,我建议使用此baeldung article。
第二个问题-异步实现有问题吗?如果没有,那么正确的方法是什么?
实现正常,没有问题。行为与线程方式不同。这些情况下的主要问题主要是什么是SLA(服务水平协议)。如果您是服务的唯一“客户,那么基本上您必须在latency or throughput之间做出选择,但是该决定只会影响您自己。通常情况并非如此,因此我建议您使用某种支持的池
第三个问题-但是,我刚刚指出,您以字符串形式读取响应流的时间大致相同。我想知道为什么会这样吗?
在两种情况下,消息很可能完全到达了(可能响应不是只是几个http包的流),但是如果您仅读取标头,则不需要解析响应并将其本身加载到CPU寄存器,从而减少了读取实际接收到的数据的等待时间。我认为这是等待时间(source和source)的不错表现:
这是一个很长的答案,所以TL.DR。 是一个真正的核心话题,它取决于很多事情:
HTTPS
连接并充当代理在您的情况下,服务器很可能是瓶颈,因为在更正的情况下,两种方法给出的结果相同(HttpResponse::getStatusLine().getStatusCode() and HttpURLConnection::getResponseCode()
)。为了给出正确的答案,您应该使用JMeter或LoadRunner等工具评估服务器性能,然后相应地确定解决方案的大小。 This article是有关数据库连接池的更多内容,但该逻辑也适用于此。