简体   繁体   English

为什么在 Spring Cloud Stream 反应式消费者中遇到异常时会收到 onComplete 信号?

[英]Why am I getting onComplete signal when an exception is encountered in Spring Cloud Stream reactive consumer?

I'm using Spring Reactor with Spring Cloud Stream (GCP Pub/Sub Binder) and running into error handling issues.我正在将 Spring Reactor 与 Spring Cloud Stream (GCP Pub/Sub Binder) 一起使用并遇到错误处理问题。 I'm able to reproduce the issue with a very simple example:我可以用一个非常简单的例子来重现这个问题:

@Bean
public Function<Flux<String>, Mono<Void>> consumer() {
    return flux -> flux
        .doOnNext(msg -> log.info("New message received: {}", msg))
        .map(msg -> {
            if (true) { 
                throw new RuntimeException("exception encountered!");
            }
            return msg;
        })
        .doOnError(throwable -> log.error("Failed to consume message", throwable))
        .then();
}

The behavior I expect is to see "Failed to consume message" print, however, that's not what appears to happen.我期望的行为是看到“无法使用消息”打印,但是,这似乎不是发生的情况。 When adding a .log() call to the chain I see onNext / onComplete signals, I would expect to see onError signals.当向链添加.log()调用时,我看到onNext / onComplete信号,我希望看到onError信号。

My actual code looks something like this:我的实际代码如下所示:

@Bean
public Function<Flux<CustomMessage>, Mono<Void>> consumer(MyService myService) {
    return flux -> flux
        .doOnNext(msg -> log.info("New message received: {}", msg))
        .flatMap(myService::processMessage) // exception happens deep in here
        .doOnError(throwable -> log.error("Failed to consume message", throwable))
        .then();
}

I noticed that deep in my service class I was attempting to do error handling on my Reactor publishers.我注意到在我的服务类深处,我试图对我的 Reactor 发布者进行错误处理。 However, the onError signal wouldn't occur when using Spring Cloud Stream.但是,使用 Spring Cloud Stream 时不会出现onError信号。 If I simply invoked my service as such myService.processMessage(msg) in a unit test and mocked the exception, my reactive chain would propagate error signals correctly.如果我在单元测试中简单地调用我的服务作为myService.processMessage(msg)并模拟异常,我的反应链将正确传播错误信号。

It seems to be an issue when I hook in to Spring Cloud Stream.当我连接到 Spring Cloud Stream 时,这似乎是一个问题。 I'm wondering if Spring Cloud Function/Stream is doing any global error wrapping?我想知道 Spring Cloud Function/Stream 是否正在执行任何全局错误包装?

In my non-trivial code I do notice this error message that may have something to do with why I'm not getting error signals?在我的非平凡代码中,我确实注意到此错误消息可能与为什么我没有收到错误信号有关?

ERROR --- onfiguration$FunctionToDestinationBinder : Failed to process the following content which will be dropped: ...

To further my confusion, I am able to get the onError signal in my reactive chain if I switch my Spring Cloud Stream binding to the non-reactive implementation as so:为了进一步混淆,如果我将 Spring Cloud Stream 绑定切换到非反应性实现,我能够在反应链中获得onError信号,如下所示:

@Bean
public Consumer<CustomMessage> consumer(MyService myService) {
    return customMessage -> Mono.just(customMessage)
        .doOnNext(msg -> log.info("New message received: {}", msg))
        .flatMap(myService::processMessage) // exception happens deep in here
        .doOnError(throwable -> log.error("Failed to consume message", throwable)) // prints successfully this time
        .subscribe();
}

So this is what I've gathered from my own investigations, maybe this might help others.所以这是我从我自己的调查中收集到的,也许这可能对其他人有所帮助。 Forewarning, I might not be using the right "Spring Reactor Language" but this is how I ended up solving it...预先警告,我可能没有使用正确的“Spring Reactor Language”,但这就是我最终解决它的方式......

In Hoxton.SR5 , an onErrorContinue was included on the reactive binding that managed the flux subscription.Hoxton.SR5onErrorContinue包含在管理通量订阅的反应式绑定中。 The problem with onErrorContinue is that it affects upstream operators by applying the BiConsumer function at the operator that failed (if supported). onErrorContinue的问题在于它通过在失败的运算符(如果支持)上应用 BiConsumer 函数来影响上游运算符。

This means that when an error occurred in our map / flatMap operators, the onErrorContinue BiConsumer would kick in and modify the downstream signal to either onComplete() ( Mono<T> ) or request(...) (if it requested a new element from a Flux<T> ).这意味着当我们的map / flatMap操作符发生错误时, onErrorContinue BiConsumer 将启动并将下游信号修改为onComplete() ( Mono<T> ) 或request(...) (如果它请求一个新元素来自Flux<T> )。 This resulted in our doOnError(...) operators not executing since there were no onError() signals.这导致我们的doOnError(...)操作符没有执行,因为没有onError()信号。

Eventually the SCS team decided toremove this error handling wrapper .最终,SCS 团队决定删除这个错误处理包装器 Hoxton.SR6 no longer has this onErrorContinue . Hoxton.SR6不再有这个onErrorContinue However, this meant that exceptions propagating up to the SCS binding would result in the Flux subscription being severed.但是,这意味着传播到 SCS 绑定的异常将导致 Flux 订阅被切断。 Subsequent messages would then have nowhere to be routed since there were no subscribers.由于没有订阅者,随后的消息将无处可路由。

This error handling has been passed along to the clients, we add an onErrorResume operator to the inner publisher to effectively drop error signals.这种错误处理已经传递给客户端,我们向内部发布者添加了一个onErrorResume操作符以有效地删除错误信号。 When an error is encountered within the myService::processMessage publisher, onErrorResume will switch publishers to the fallback publisher that was passed in as a parameter and resume from that point in the operator chain.当在myService::processMessage发布者中遇到错误时, onErrorResume会将发布者切换到作为参数传入的回退发布者,并从操作员链中的那个点恢复。 In our case, this fallback publisher simply returns Mono.empty() which allows us to drop the error signals while still allowing internal error handling mechanisms to operate while also not affecting the outer source publisher.在我们的例子中,这个回退发布者只返回Mono.empty() ,这允许我们删除错误信号,同时仍然允许内部错误处理机制运行,同时也不影响外部源发布者。

onErrorResume Example/Explanation onErrorResume示例/说明

The above technique can be illustrated with a very simple example.上述技术可以用一个非常简单的例子来说明。

Flux.just(1, 2, 3)
    .flatMap(i -> i == 2
        ? Mono.error(new RuntimeException("error")
        : Mono.just(i))
    .onErrorResume(t -> Flux.just(4, 5, 6))
    .doOnNext(i -> log.info("Element: {}", i))
    .subscribe();

The Flux<Integer> above will output the following:上面的Flux<Integer>将输出以下内容:

Element: 1
Element: 4
Element: 5
Element: 6

Since an error is encountered at element 2 , onErrorResume fallback kicks in and the new publisher becomes Flux.just(4, 5, 6) effectively resuming from the fallback.由于在元素2遇到错误, onErrorResume回退开始,新发布者变为Flux.just(4, 5, 6)有效地从回退中恢复 In our case, we don't want to affect the source publisher (ie Flux.just(1, 2, 3) ).在我们的例子中,我们不想影响源发布者(即Flux.just(1, 2, 3) )。 We want to just drop the erroneous element ( 2 ) and continue to the next element ( 3 ).我们只想删除错误的元素 ( 2 ) 并继续下一个元素 ( 3 )。

We can't simply change Flux.just(4, 5, 6) to Flux.empty() or Mono.empty() as so:我们不能简单地将Flux.just(4, 5, 6)更改为Flux.empty()Mono.empty()如下所示:

Flux.just(1, 2, 3)
    .flatMap(i -> i == 2
        ? Mono.error(new RuntimeException("error")
        : Mono.just(i))
    .onErrorResume(t -> Mono.empty())
    .doOnNext(i -> log.info("Element: {}", i))
    .subscribe();

This would cause the following to be output:这将导致以下输出:

Element: 1

This is because onErrorResume has replaced the upstream publishers with the fallback publisher (ie Mono.empty() ) and resumed from that point on.这是因为onErrorResume已经用回退发布者(即Mono.empty() )替换了上游发布者并Mono.empty()恢复

To achieve our desired output of:为了实现我们想要的输出:

Element: 1
Element: 3

We must place the onErrorResume operator on the inner publisher of the flatMap :我们必须将onErrorResume操作符放在flatMap的内部发布者上:

public Mono<Integer> func(int i) {
    return i = 2 ? Mono.error(new RuntimeException("error")) : Mono.just(i);
}

Flux.just(1, 2, 3)
    .flatMap(i -> func(i)
        onErrorResume(t -> Mono.empty()))
    .doOnNext(i -> log.info("Element: {}", i))
    .subscribe();

Now, the onErrorResume only effects the inner publisher returned by func(i) .现在, onErrorResume只影响func(i)返回的内部发布者。 If an error occurs from operators in func(i) , onErrorResume will fallback to Mono.empty() effectively completing the Mono<T> without blowing up.如果func(i)运算符发生错误, onErrorResume将回Mono.empty()有效地完成Mono<T>而不会Mono.empty() This also still allows error handling operators (eg doOnError ) within func(i) to be applied before the fallback runs.这也仍然允许错误处理运营商(例如doOnErrorfunc(i)到回退运行之前被应用。 This is because, unlike onErrorContinue , it does not affect upstream operators and change the next signal at the location of the error.这是因为,与onErrorContinue不同,它不会影响上游操作符并更改错误位置处的下一个信号。

Final Solution最终解决方案

Reusing the code-snippet in my question, I've upgraded my Spring Cloud version to Hoxton.SR6 and changed the code to something like this:在我的问题中重用代码片段,我已将 Spring Cloud 版本升级到Hoxton.SR6并将代码更改为如下所示:

@Bean
public Function<Flux<CustomMessage>, Mono<Void>> consumer(MyService myService) {
    return flux -> flux
        .doOnNext(msg -> log.info("New message received: {}", msg))
        .flatMap(msg -> myService.processMessage(msg)
            .onErrorResume(throwable -> Mono.empty())
        )
        .then();
}

Note that the onErrorResume is on the inner publisher (inside the flatMap ).请注意, onErrorResume位于内部发布者(在flatMap )。

I think the problem exists in the following code:我认为问题存在于以下代码中:

    .map(msg -> new RuntimeException("exception encountered!"))

The lambda in your map line is returning an exception, not throwing one.地图行中的 lambda 返回异常,而不是抛出异常。

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

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