等待多个Spring WebClient Mono响应

时间:2019-10-17 21:46:01

标签: spring-webflux project-reactor

我试图在微服务应用程序中调用外部服务,以并行获取所有响应,并在开始其他计算之前将它们组合起来。我知道我可以在每个Mono对象上使用block()调用,但是这样做会打消使用反应式api的目的。是否有可能并行触发所有请求并在一个点上合并它们。

示例代码如下。在这种情况下,将在实际响应出现之前打印“完成”。我也知道订阅电话是不阻塞的。

我希望在收集所有响应后打印“完成”,因此需要某种阻止。但是不想阻止每个请求

final List<Mono<String>> responseOne = new ArrayList<>();
    IntStream.range(0, 10).forEach(i -> {
        Mono<String> responseMono =
                WebClient.create("https://jsonplaceholder.typicode.com/posts")
                        .post()
                        .retrieve()
                        .bodyToMono(String.class)
                ;
        System.out.println("create mono response lazy initialization");
        responseOne.add(responseMono);
    });

    Flux.merge(responseOne).collectList().subscribe( res -> {

        System.out.println(res);
    });
    System.out.println("Done");

根据建议,我提出了这个建议,对我来说很有效。

StopWatch watch = new StopWatch();
    watch.start();
    final List<Mono<String>> responseOne = new ArrayList<>();
    IntStream.range(0, 10).forEach(i -> {
        Mono<String> responseMono =
                WebClient.create("https://jsonplaceholder.typicode.com/posts")
                        .post()
                        .retrieve()
                        .bodyToMono(String.class);
        System.out.println("create mono response lazy initialization");
        responseOne.add(responseMono);
    });
    CompletableFuture<List<String>> futureCount = new CompletableFuture<>();
    List<String>  res = new ArrayList<>();
    Mono.zip(responseOne, Arrays::asList)
            .flatMapIterable(objects -> objects) // make flux of objects
            .doOnComplete(() -> {
                futureCount.complete(res);
            }) // will be printed on completion of the flux created above
            .subscribe(responseString -> {
                        res.add((String) responseString);
                    }
            );

    watch.stop();
    List<String> response = futureCount.get();
    System.out.println(response);
    // do rest of the computation
    System.out.println(watch.getLastTaskTimeMillis());

2 个答案:

答案 0 :(得分:2)

  1. 如果您希望并行进行呼叫,则最好使用Mono.zip
  2. 现在,您希望在收集所有回复之后打印Done

因此,您可以按以下方式修改代码

final List<Mono<String>> responseMonos = IntStream.range(0, 10).mapToObj(
        index -> WebClient.create("https://jsonplaceholder.typicode.com/posts").post().retrieve()
            .bodyToMono(String.class)).collect(Collectors.toList()); // create iterable of mono of network calls

Mono.zip(responseMonos, Arrays::asList) // make parallel network calls and collect it to a list
        .flatMapIterable(objects -> objects) // make flux of objects
        .doOnComplete(() -> System.out.println("Done")) // will be printed on completion of the flux created above
        .subscribe(responseString -> System.out.println("responseString = " + responseString)); // subscribe and start emitting values from flux

在反应式代码中显式调用subscribeblock也不是一个好主意。

答案 1 :(得分:0)

  

是否有可能并行触发所有请求并将它们组合在一个点上。

这正是您的代码已经在做的事情。如果您不相信我,请在.delayElement(Duration.ofSeconds(2))通话后坚持使用bodyToMono()。您会看到列表在2秒钟后打印出来,而不是20秒(这是顺序执行10次的结果。)

合并部分正在您的Flux.merge().collectList()通话中。

  

在这种情况下,在实际响应出现之前会打印“完成”。

这是意料之中的,因为您的上一个System.out.println()调用正在反应式回调链之外执行。如果您希望在打印列表后打印“完成”(您已经混淆了传递给您的s调用方的使用者的变量名subscribe()),则需要将其放入其中该消费者,而不是外部消费者。

如果您要使用命令式API,因此需要阻止列表,则可以执行以下操作:

List<String> list = Flux.merge(responseOne).collectList().block();

...它仍将并行执行调用(因此仍会为您带来一些好处),但随后阻塞直到所有调用完成并合并到一个列表中。 (但是,如果您只是将电抗器用于这种类型的用途,是否值得商while。)