我正在尝试选择一种最好的方法来并行处理大量的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");
});
在多次运行中,我注意到传统方法的完成速度几乎是异步/未来方法的两倍。
尽管我期望专用线程运行得更快,但区别应该是如此显著,还是异步实现出了问题?如果没有,在这里什么是正确的方法?
1条答案
按热度按时间b0zn9rqh1#
这个问题取决于很多因素:
硬件
操作系统(及其配置)
jvm实现
网络设备
服务器行为
第一个问题——这种差异应该如此显著吗?
取决于负载、池大小和网络,但在每个方向上都可能远远超过观察到的因子2(有利于异步或线程解决方案)。根据你后来的评论,这种差异更多的是因为行为不端,但为了论证,我将解释可能的情况。
专用线程可能是一个相当大的负担(中断处理和线程调度由操作系统完成,以防在委派这些任务时使用oracle[hotspot]jvm。)如果线程过多,操作系统/系统可能会变得无响应,从而减慢批处理(或其他任务)的速度。有很多关于线程管理的管理任务,这就是为什么线程(和连接)池是一件事。虽然一个好的操作系统应该能够处理几千个并发线程,但是总是有可能出现一些限制或(内核)事件。
这就是池和异步行为派上用场的地方。例如,有一个由10个物理线程组成的池来完成所有工作。如果某个对象被阻塞(在本例中等待服务器响应),它将处于“阻塞”状态(参见图),下面的任务将让物理线程执行一些工作。当一个线程收到通知(数据到达)时,它将变为“可运行”(从这一点上,池机制能够获取它[这可能是os或jvm实现的解决方案])。对于线程状态的进一步阅读,我建议使用w3rescue。为了更好地理解线程池,我推荐这篇baeldung文章。
第二个问题-异步实现是否有问题?如果没有,在这里什么是正确的方法?
执行还可以,没有问题。这种行为与线程方式完全不同。这些情况下的主要问题主要是什么是sla-s(服务级别协议)。如果您是服务的唯一“客户”,那么基本上您必须在延迟或吞吐量之间做出决定,但是这个决定只会影响您。大多数情况下并非如此,因此我建议您使用的库支持某种类型的池。
第三个问题-但是我注意到所用的时间与将响应流作为字符串读取的时间大致相同。我想知道这是为什么?
消息很可能在这两种情况下都完全到达(可能响应不是一个流,只是几个http包),但是如果只读取头,则不需要解析响应本身并将其加载到cpu寄存器上,从而减少读取实际接收数据的延迟。我认为这是一个很酷的延迟代表(来源和来源):
这是一个很长的答案,所以tl.dr.:缩放是一个非常核心的主题,它取决于很多事情:
硬件:物理核数、多线程容量、内存速度、网络接口
操作系统(及其配置):线程管理、中断处理
jvm实现:线程管理(内部或外包给操作系统),更不用说gc和jit配置了
网络设备:有些限制来自给定ip的并发连接,有些限制来自非池
HTTPS
连接并充当代理服务器行为:池工人或按请求工人等
在您的案例中,服务器很可能是瓶颈,因为这两种方法在更正的案例中给出了相同的结果(
HttpResponse::getStatusLine().getStatusCode() and HttpURLConnection::getResponseCode()
). 要给出正确的答案,您应该使用jmeter或loadrunner等工具来衡量服务器的性能,然后相应地调整解决方案的大小。本文将更多地介绍db连接池,但其逻辑也适用于这里。