Java 使用WebFlux对特定映射的并行GET请求

Java 使用WebFlux对特定映射的并行GET请求,java,spring-webflux,project-reactor,spring-webclient,Java,Spring Webflux,Project Reactor,Spring Webclient,我想与WebClient同时调用独立请求。我以前使用RestTemplate的方法是在等待响应时阻塞线程。所以我发现,使用ParallelFlux的WebClient可以使用一个线程来提高效率,因为它应该使用一个线程来调度多个请求 我的端点请求一个id和位置的元组 fooFlux方法将在具有不同参数的循环中被调用数千次。返回的映射将根据存储的引用值进行断言 此代码以前的尝试导致重复的API调用。 但仍然存在一个缺陷。映射键集的大小通常小于集合位置的大小。事实上,生成的贴图的大小正在更改。此外,它

我想与WebClient同时调用独立请求。我以前使用RestTemplate的方法是在等待响应时阻塞线程。所以我发现,使用ParallelFlux的WebClient可以使用一个线程来提高效率,因为它应该使用一个线程来调度多个请求

我的端点请求一个id和位置的元组

fooFlux方法将在具有不同参数的循环中被调用数千次。返回的映射将根据存储的引用值进行断言

此代码以前的尝试导致重复的API调用。 但仍然存在一个缺陷。映射键集的大小通常小于集合位置的大小。事实上,生成的贴图的大小正在更改。此外,它有时是正确的。因此,在方法返回映射后,subscripton finishing可能会出现问题

public Map<String, ServiceDescription> fooFlux(String id, Set<String> locations) {
    Map<String, ServiceDescription> mapping = new HashMap<>();
    Flux.fromIterable(locations).parallel().runOn(Schedulers.boundedElastic()).flatMap(location -> {
        Mono<ServiceDescription> sdMono = getServiceDescription(id, location);
        Mono<Mono<ServiceDescription>> sdMonoMono = sdMono.flatMap(item -> {
            mapping.put(location, item);
            return Mono.just(sdMono);
        });
        return sdMonoMono;
    }).then().block();
    LOGGER.debug("Input Location size: {}", locations.size());
    LOGGER.debug("Output Location in map: {}", mapping.keySet().size());
    return mapping;
}
处理Get请求

private Mono<ServiceDescription> getServiceDescription(String id, String location) {
    String uri = URL_BASE.concat(location).concat("/detail?q=").concat(id);
    Mono<ServiceDescription> serviceDescription =
                    webClient.get().uri(uri).retrieve().onStatus(HttpStatus::isError, clientResponse -> {
                        LOGGER.error("Error while calling endpoint {} with status code {}", uri,
                                        clientResponse.statusCode());
                        throw new RuntimeException("Error while calling Endpoint");
                    }).bodyToMono(ServiceDescription.class).retryBackoff(5, Duration.ofSeconds(15));
    return serviceDescription;
}

当您订阅生产者时,将执行反应代码。 Block确实订阅了,因为在Mono上调用Block两次,但再次返回Mono,然后在ParallelFlux上调用Block,所以Mono会执行两次

    List<String> resultList = listMono.block();
    mapping.put(location, resultList);
    return listMono;

这样说,反应式编程模型相当复杂,所以考虑使用@ AsYNC和Eng/AcyCultREST,如果这只是与其他人建议的那样并行调用远程调用的话。 您仍然可以使用WebClient RestTemplate,它似乎即将被弃用,但只需在bodyToMono之后调用block即可。

public Map fooFluxString id,Set locations{ 返回通量。来自可分配位置 .flatMaplocation->getServiceDescriptionid,location.mapsd->Tuples.oflocation,sd .collectMapTuple2::getT1,Tuple2::getT2 块 }
注意:flatMap运算符与WebClient调用相结合,可实现并发执行,因此无需使用ParallelFlux或任何调度程序。

为什么使用JsonNode.class而不将其序列化/反序列化为具体对象?为什么要使用反应式编程来解决使用@Async可以解决的问题呢。反应式编程不是异步编程。我使用了JsonNode.class,因为接收到的JSON模型非常庞大,我只需要一点点。我提出了反应式编程是因为贝尔东的一篇文章。我想提高下载速度。ParallelStreams中的RestTemplate方法使我在网卡上的下载速度达到了10Mbit/s到200Mbit/s之间。取决于每个id的位置数量。但这在1到4000REST之间变化。客户端不影响下载速度。网络带宽影响速度。因此,您使用的客户端不会影响任何下载速度。如果你只需要一小部分,谁说你需要声明整个对象?只需创建一个包含所需小部分的类。是的,请使用WebClient,但您需要知道反应式编程和并发编程之间的区别。反应式编程的全部目的是不阻塞,并尽可能利用线程执行串行和并行任务。虽然并发编程是做您想做的事情,但同时生成线程和获取内容。反应式编程可以串行和并行地完成任务,以解决您给他们的任务。但它并不是主要用于执行异步任务。如果您需要收集upp所有结果,并获取具体值以在一个大blob中返回到调用客户端,而不是将结果流式传输到调用客户端,那么在您的非反应性应用程序中,需要使用block。但正如您所做的那样,它被放置在自己的调度程序中。但我建议使用boundedElastic调度程序,您选择的调度程序将无限地使用线程,在最坏的情况下,会导致线程不足并导致应用程序崩溃。谢谢您的回复。这有助于我消除重复的请求。为了防止出现空映射,我必须将Mono.justlistMono vlaue绑定到一个变量,该变量需要在外部平面映射的末尾返回。起初,我试图返回listMono,但没有使用Mono.justlistMono创建新的Mono。这也导致了重复的请求。你能解释一下这种行为吗?不幸的是,填充的地图没有填充完整。键集映射的大小通常小于集合位置的大小。事实上,生成的贴图的大小正在更改。我是否需要内部订阅或其他东西来确保在方法返回之前完全填充映射?我更新了我的问题,以反映我在回答时所做的更改。谢谢马丁,我想这就是重点。我无法在该上下文中找到或正确使用collectMap方法。您能详细说明一下为什么不将ParallelFlux与调度器一起使用吗?我认为工作单元的共享线程池在I/O情况下会加快速度 用于CPU密集型工作。并发IO可以在没有它的情况下实现,因为在等待外部资源时CPU不被使用,所以可以用非常少的线程数实现高并发量。并行流量只会使事情变得比需要的更复杂。
    listMono.map(resultList -> {
       mapping.put(location, resultList);
       return Mono.just(listMono);
    });