简体   繁体   English

立即从两个 Monos 返回第一个发出的值,同时继续异步处理另一个

[英]Immediately return first emitted value from two Monos while continuing to process the other asynchronously

I have two data sources, each returning a Mono:我有两个数据源,每个都返回一个 Mono:

class CacheCustomerClient {
    Mono<Entity> createCustomer(Customer customer)
}

class MasterCustomerClient {
    Mono<Entity> createCustomer(Customer customer)
}

Callers to my application are hitting a Spring WebFlux controller:我的应用程序的调用者正在点击 Spring WebFlux controller:

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Flux<Entity> createCustomer(@RequestBody Customer customer) {
    return customerService.createNewCustomer(entity);
}

As long as either data source successfully completes its create operation, I want to immediately return a success response to the caller, however, I still want my service to continue processing the result of the other Mono stream, in the event that an error was encountered, so it can be logged.只要任一数据源成功完成其创建操作,我想立即向调用方返回成功响应,但是,我仍然希望我的服务继续处理其他 Mono stream 的结果,以防遇到错误, 所以它可以被记录下来。

The problem seems to be that as soon as a value is returned to the controller, a cancel signal is propagated back through the stream by Spring WebFlux and, thus, no information is logged about a failure.问题似乎是,一旦值返回到 controller,取消信号就会通过 Spring WebFlux 通过 stream 传回,因此,没有记录有关故障的信息。

Here's one attempt:这是一种尝试:

public Flux<Entity> createCustomer(final Customer customer) {
        var cacheCreate = cacheClient
                .createCustomer(customer)
                .doOnError(WebClientResponseException.class,
                    err -> log.error("Customer creation failed in cache"));
        var masterCreate = masterClient
                .createCustomer(customer)
                .doOnError(WebClientResponseException.class,
                    err -> log.error("Customer creation failed in master"));

        return Flux.firstWithValue(cacheCreate, masterCreate)
                .onErrorMap((err) -> new Exception("Customer creation failed in cache and master"));
    }

Flux.firstWithValue() is great for emitting the first non-error value, but then whichever source is lagging behind is cancelled, meaning that any error is never logged out. Flux.firstWithValue()非常适合发出第一个非错误值,但随后将取消任何落后的来源,这意味着永远不会注销任何错误。 I've also tried scheduling these two sources on their own Schedulers and that didn't seem to help either.我也试过在他们自己的调度程序上安排这两个源,但似乎也没有帮助。

How can I perform these two calls asynchronously, and emit the first value to the caller, while continuing to listen for emissions on the slower source?我如何异步执行这两个调用,并将第一个值发送给调用者,同时继续侦听较慢源上的发送?

You can achieve that by transforming you operators to "hot" publishers using share() operator:您可以通过使用share()运算符将您的运算符转换为“热门”发布者来实现这一点:

  1. First subscriber launch the upstream operator, and additional subscribers get back result cached from the first subscriber:第一个订阅者启动上游操作符,其他订阅者从第一个订阅者那里取回缓存的结果:

    Further Subscriber will share [...] the same result.其他订阅者将共享 [...] 相同的结果。

  2. Once a second subscription has been done, the publisher is not cancellable :一旦完成第二次订阅,发布者就不可取消

    It's worth noting this is an un-cancellable Subscription.值得注意的是,这是一个不可取消的订阅。

So, to achieve your requirement:所以,为了达到你的要求:

  1. Apply share() on each of your operators在每个操作员上应用 share()
  2. Launch a subscription on shared publishers to trigger processing在共享发布者上启动订阅以触发处理
  3. Use shared operators in your pipeline (here firstWithValue).在您的管道中使用共享运算符(此处为 firstWithValue)。

Sample example:示例示例:

import java.time.Duration;
import reactor.core.publisher.Mono;

public class TestUncancellableMono {

    // Mock a mono successing quickly
    static Mono<String> quickSuccess() {
        return Mono.delay(Duration.ofMillis(200)).thenReturn("SUCCESS !");
    }

    // Mock a mono taking more time and ending in error.
    static Mono<String> longError() {
        return Mono.delay(Duration.ofSeconds(1))
                   .<String>then(Mono.error(new Exception("ERROR !")))
                    .doOnCancel(() -> System.out.println("CANCELLED"))
                    .doOnError(err -> System.out.println(err.getMessage()));
    }

    public static void main(String[] args) throws Exception {
        // Transform to hot publisher
        var sharedQuick = quickSuccess().share();
        var sharedLong  = longError().share();

        // Trigger launch
        sharedQuick.subscribe();
        sharedLong.subscribe();

        // Subscribe back to get the cached result
        Mono
                .firstWithValue(sharedQuick, sharedLong)
                .subscribe(System.out::println, err -> System.out.println(err.getMessage()));

        // Wait for subscription to end.
        Thread.sleep(2000);
    }
}

The output of the sample is:样本的output为:

SUCCESS !
ERROR !

We can see that error message has been propagated properly, and that upstream publisher has not been cancelled.我们可以看到错误消息已经正确传播,并且上游发布者没有被取消。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM