在Java8中与ExecutorService并行发送查询

在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

我正在用Java编写一个web应用程序(即使用)。在这个web应用程序中,我有一个端点,当被调用时,它应该向服务器发送一组其他请求。由于这些请求的数量可能会增加,我决定使用Java8中引入的Java并发API并行发送这些请求。我并行发送多个请求的代码如下:

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我又玩了一些代码,似乎服务器在合理的时间内返回了响应(不是同时发送所有响应),但是客户端收集结果的时间比每个请求都要长,所有请求都彼此相似。我将更新此问题。