在Java8中与ExecutorService并行发送查询
我正在用Java编写一个web应用程序(即使用)。在这个web应用程序中,我有一个端点,当被调用时,它应该向服务器发送一组其他请求。由于这些请求的数量可能会增加,我决定使用Java8中引入的Java并发API并行发送这些请求。我并行发送多个请求的代码如下:在Java8中与ExecutorService并行发送查询,java,http,concurrency,parallel-processing,java-8,Java,Http,Concurrency,Parallel Processing,Java 8,我正在用Java编写一个web应用程序(即使用)。在这个web应用程序中,我有一个端点,当被调用时,它应该向服务器发送一组其他请求。由于这些请求的数量可能会增加,我决定使用Java8中引入的Java并发API并行发送这些请求。我并行发送多个请求的代码如下: public List<String> searchAll(List<String> keywords) { ExecutorService executor = Executors.newWorkSteali
public List<String> searchAll(List<String> keywords) {
ExecutorService executor = Executors.newWorkStealingPool();
List<Callable<List<String>>> tasks = new ArrayList<>();
for (String key : keywords) {
tasks.add(() -> {
LOGGER.info("Sending query for key: " + key);
return sendSearchQuery(key);
});
}
List<String> all = new ArrayList<>();
try {
executor.invokeAll(tasks)
.stream()
.map(future -> {
try {
return future.get();
}
catch (Exception e) {
throw new IllegalStateException(e);
}
})
.forEach((list) ->
{
LOGGER.info("Received list: " + list);
all.addAll(list);
});
} catch (InterruptedException e) {
e.printStackTrace();
}
return all;
}
private List<String> sendSearchQuery(String query) throws UnirestException {
long startTime = System.nanoTime();
HttpResponse<JsonNode> response = Unirest.get(SEARCH_URL)
.queryString("q", query).asString();
Map<String, Object> result = JsonHelper.toMap(response.getBody());
// Get get = Http.get(SEARCH_URL + "?q=" + query);
// Map<String, Object> result = JsonHelper.toMap(get.text());
LOGGER.info("Query received in " + (System.nanoTime() - startTime) / 1000000 + " ms for key: " + query);
return (List<String>) result.get("result");
}
[ForkJoinPool-2-worker-1] INFO app.managers.SearchManager - Sending query for key: sky
[ForkJoinPool-2-worker-2] INFO app.managers.SearchManager - Sending query for key: outdoor
[ForkJoinPool-2-worker-3] INFO app.managers.SearchManager - Sending query for key: bridge
[ForkJoinPool-2-worker-0] INFO app.managers.SearchManager - Sending query for key: water
[ForkJoinPool-2-worker-0] INFO app.managers.SearchManager - Query received in 1331 ms for key: water
[ForkJoinPool-2-worker-0] INFO app.managers.SearchManager - Sending query for key: building
[ForkJoinPool-2-worker-1] INFO app.managers.SearchManager - Query received in 1332 ms for key: sky
[ForkJoinPool-2-worker-2] INFO app.managers.SearchManager - Query received in 1332 ms for key: outdoor
[ForkJoinPool-2-worker-3] INFO app.managers.SearchManager - Query received in 1332 ms for key: bridge
[ForkJoinPool-2-worker-0] INFO app.managers.SearchManager - Query received in 302 ms for key: building
[1324676647@qtp-178658894-0] INFO app.managers.SearchManager - Received list: [16973, 4564, 12392, 1195, 1207, 682, 10518, 10532, 10545, 19328, 10524, 10537, 10551, 19334, 10522, 10535, 10548, 19332, 10521, 10534]
[1324676647@qtp-178658894-0] INFO app.managers.SearchManager - Received list: []
[1324676647@qtp-178658894-0] INFO app.managers.SearchManager - Received list: [4303, 2844, 4366]
[1324676647@qtp-178658894-0] INFO app.managers.SearchManager - Received list: [9490, 1638, 20006, 17715, 17758, 18788, 6071, 11230, 13384, 4940, 18039, 17871, 16629, 6148, 19172, 4263, 4569, 8396, 18643, 4904]
[1324676647@qtp-178658894-0] INFO app.managers.SearchManager - Received list: [17306, 17303, 17305, 17304, 16062, 16156, 16153, 16154, 16061, 9098, 2491, 4368, 22134, 1008, 16152, 16151, 16148, 16155, 16147, 16149]
正如您所看到的,我使用了两个不同的Http库(和)来查看问题是否出在我使用的库上,但情况似乎并非如此,因为它们都会产生相同的问题
这里的问题是,第一个n
(机器上的处理器数量)查询同时开始和结束。这是正常的,但也需要更长的时间。假设在正常情况下,单个请求需要t
时间。在这种情况下,第一个n
查询各占用n*t
时间,其余查询各占用t
时间。我是否错误地使用了并发API
编辑:运行在SEARCH\u URL
上的服务器部署在Azure上,它可以处理多个请求
我还尝试了以下方法:
- 使用
,但是我使用的ExecutorService.newFixedThreadPool()
似乎不是问题的原因Executor
- 调用
而不是invokeAny()
,但是前者会阻塞主线程,直到其中一个任务完成,并且只返回该任务的结果invokeAll()
编辑2:因此我对服务器和当前正在使用的应用程序都进行了操作。奇怪的是,服务器在不同的时间响应n个请求,但是应用程序在一个时间段后收到这些响应,该时间段从第一个请求到达服务器开始,到第n个响应到达应用程序结束。我对这种行为没有任何解释。
invokeAll(Collection您是否看过为java 8引入的completableFuture框架?我可以帮助您尝试以异步方式发送所有内容
invokeAll(Collection<? extends Callable<T>> tasks)
List<CompletableFuture<List<String>>> futures = keywords.parallelStream()
.map(key -> CompletableFuture.supplyAsync(() -> sendSearchQuery(key), executor))
.collect(toList());
CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
try {
allOf.join();
} catch (Exception e){
e.printStackTrace();
}
List<String> all = futures.stream().filter(CompletableFuture::isCompletedExceptionally)
.flatMap(future -> future.join().stream())
.collect(toList());
return all;
List futures=keywords.parallelStream()
.map(key->CompletableFuture.supplyAsync(()->sendSearchQuery(key),executor))
.collect(toList());
CompletableFuture allOf=CompletableFuture.allOf(futures.toArray(新的CompletableFuture[0]);
试一试{
allOf.join();
}捕获(例外e){
e、 printStackTrace();
}
List all=futures.stream().filter(CompletableFuture::isCompletedExceptionally)
.flatMap(future->future.join().stream())
.collect(toList());
全部归还;
这将发送所有异步搜索,然后调用allOf.join(),等待所有返回
最后一个流将每个结果映射回一个列表并返回您是对的,文档中确实说“当所有查询都完成时”,但是剩余的查询会正常发送,并且未来的
对象会及时返回,没有任何问题。此外,似乎没有任何替代invokeAll()的方法
它执行彼此独立的任务。服务器部署在Azure上,可以处理多个请求。invokeAny()
就在那里。如果使用Executors.newFixedThreadPool()
您可以一个接一个地提交多个任务,这些任务将并行执行。而且,时间安排告诉我们,除了等待所有四个请求完成这一事实之外,什么都没有告诉我们。invokeAny()
运行一系列任务,只返回第一个要完成的任务的Future
对象。即使它们的返回类型也不同。在本例中,这不太明显,但如果我运行20个任务,则更明显的是,剩下的任务是并行运行的,没有问题。您对fixedthreadpool或一个接一个地提交任务,但它们将并行执行。如果您只是放置固定睡眠而不是http调用,会发生什么情况?这样,我们可以确定这是executor服务问题还是服务器问题。此外,请将日期时间添加到日志并打印输出。这样可以更容易地可视化您解释的问题@MadPiranha当我不发送请求时,它会按预期工作。问题似乎确实与服务器本身有关。我在localhost上运行服务器,第一个n
响应确实会在更长的时间内发送。具体取决于“前n个请求”的内容也就是说,您的问题可能是由于“java.util.concurrent”中相关类的加载和初始化造成的软件包。我记得在某个地方读到过它,但遗憾的是,我记不起它的确切位置。如果我们知道延迟来自服务器端,那么我们需要看看这一部分!请更新问题。@MadPiranha我又玩了一些代码,似乎服务器在合理的时间内返回了响应(不是同时发送所有响应),但是客户端收集结果的时间比每个请求都要长,所有请求都彼此相似。我将更新此问题。