Kotlin Spring Cloud Stream被动-处理消息异常

Kotlin Spring Cloud Stream被动-处理消息异常,kotlin,spring-cloud-stream,spring-rabbit,reactor,Kotlin,Spring Cloud Stream,Spring Rabbit,Reactor,我目前正在Kotlin的一个项目中工作,该项目使用rabbit with reactor来接收某些DTO类型的消息,如果它们符合某些标准,则发送它们。在测试代码的过程中,我尝试模拟错误的消息输入(因为消息来自外部服务),并查看订阅者的行为。收到错误的输入消息后,订户停止侦听任何新输入,并引发以下异常: org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers for channel

我目前正在Kotlin的一个项目中工作,该项目使用rabbit with reactor来接收某些DTO类型的消息,如果它们符合某些标准,则发送它们。在测试代码的过程中,我尝试模拟错误的消息输入(因为消息来自外部服务),并查看订阅者的行为。收到错误的输入消息后,订户停止侦听任何新输入,并引发以下异常:

 org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers for channel 'application.calculateAverage-in-0'.; nested exception is org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers, failedMessage=GenericMessage
然后,我尝试运行fromspring,将供应商更改为在第一次发送时发送错误数据,然后发送有效数据并查看其行为

在供应商方面,我添加了一个索引器,仅在第一次运行时发送错误消息

//Following source and sinks are used for testing only.
//Test source will send data to the same destination where the processor receives data
//Test sink will consume data from the same destination where the processor produces data
// ------ New Code -------
static int x = 0;
// ------ END New Code -------

static class TestSource {

    private AtomicBoolean semaphore = new AtomicBoolean(true);
    private Random random = new Random();
    private int[] ids = new int[]{100100, 100200, 100300};

    @Bean
    public Supplier<?> sendTestData() {

        return () -> {
            // ------ New Code -------
            if(x==0) {
                return "hey";
            }
            x++;
            // ------ END New Code -------
            int id = ids[random.nextInt(3)];
            int temperature = random.nextInt((102 - 65) + 1) + 65;
            Sensor sensor = new Sensor();
            sensor.setId(id);
            sensor.setTemperature(temperature);
            return sensor;
        };
    }
}

我的问题是如何处理“幕后”发生的异常,如输入解析?

鉴于反应式编程的性质,这是一个棘手的问题,因此我们可能需要将此讨论纳入一个问题,请放心,但我的看法如下

被动功能和命令功能的根本区别在于工作单元的概念。对于命令式函数,工作单元是单个消息,因此框架保持对流的持续控制,只通过消息将流传递给函数一瞬间。所以你们会期望并且理所当然地这样,无论错误发生在哪里,我们都会有一些错误处理的东西——我们确实这样做了

由于工作单元是整个流,而函数仅作为框架提供的流和用户定义的流操作之间的连接件,因此使用反应式函数,世界将完全改变。在这一点上,s-c-stream无法控制用户的行为,因此我们的一般建议,特别是考虑到在错误处理方面反应式API的丰富性,是让用户自己处理。但要明白,这并不是因为我们不想,而是我们不能,因为我们在那个时候无法看到溪流。 您的问题确实相当独特,因为异常发生在执行您定义的步骤之前,特别是我们提供的类型转换。事实上,我们可以做一些事情来帮助解决这个问题,但我们仍在寻求共识,这些事情应该是什么,直到我们迅速失败才是解决办法。您可以通过修复输入来克服它,因为它显然不是JSON和/或放松您的函数签名
function
,并自己处理类型转换

无论如何,正如你所看到的,我愿意接受建议,所以请随时提出、发布并提供你的意见。

@Bean
@Bean
Function<Flux<?>, Flux<?>> myFunction() {
    return messageFlux
        .onErrorContinue(MessageConversionException.class, (e, obj) -> {log.warn(...)})
        ...
}
函数>我的函数(){ 返回消息流量 .onErrorContinue(MessageConversionException.class,(e,obj)->{log.warn(…)} ... }

这就是以前对我有用的东西。这将允许上游继续处理转换异常。

以下是我的观点和处理所有这些问题的策略:

首先,我尝试将已知的错误输入重定向到错误主题(就像这里已经建议的那样):

私有流量知识共享(流量点流量){
返回点Flux.onErrorContinue(
此::IsKnownexception应被定向到错误频道,
(e,o)->streamBridge.send(appProperties.getErrorsBindingName(),o));
}
如果未处理异常,则取消订阅。这就是为什么我们会看到
调度程序没有订户
错误

失去订阅后不重新订阅的一个奇怪的副作用是,经过几次尝试处理消息后,抛出
调度程序没有订阅者
异常后,卡夫卡消息被确认,实际上失去了消息

我们可以使用
Flux#retry()
变体,但这只会使订阅者重新订阅,而不会尝试重新处理相同的消息

我的方法是避免反应堆重试,而是在发生不可恢复的异常时退出应用程序,依靠底层平台重新启动应用程序,希望它在回收后工作:

    @EventListener
    public void onApplicationStartedRegisterReactorHookToStopApplicationIfErrorDropped(ApplicationStartedEvent e) {
        Hooks.onErrorDropped(t -> {
            LOG.error("Unable to recover from exception.", t);
            e.getApplicationContext().close();
            System.exit(1);
        });
    }
我还关闭了自动提交偏移量:
spring.cloud.stream.kafka.bindings..consumer.autocommitofset:false
,特别是当我依赖于Reactor的
.bufferTimeout
,我注意到它会触发每条消息的消息确认,即使批处理可能失败


总的来说,我觉得被动的方式不是SpringCloudStreams的方式。至少目前还没有。

spring代码如何使用
函数
bean?添加
onErrorContinue
onErrorResume
是否有助于解决此行为?是的,在某些情况下会这样,我们以前也有过,但在其他情况下会失败。例如,您可能正在功能代码中累积一个缓冲区或窗口,而一些传入消息无法转换。我们该怎么办?继续?简历我希望你能理解我们的困境这肯定是一个困境,但中止联系似乎是最糟糕的选择——继续还是继续——两者似乎都更好。我提出了一个问题:@OlegZhurakousky☝️我只是想澄清一下。上面的代码不会导致上游转换,因为不存在类型信息,所以没有要转换的内容
@Bean
Function<Flux<?>, Flux<?>> myFunction() {
    return messageFlux
        .onErrorContinue(MessageConversionException.class, (e, obj) -> {log.warn(...)})
        ...
}
    @EventListener
    public void onApplicationStartedRegisterReactorHookToStopApplicationIfErrorDropped(ApplicationStartedEvent e) {
        Hooks.onErrorDropped(t -> {
            LOG.error("Unable to recover from exception.", t);
            e.getApplicationContext().close();
            System.exit(1);
        });
    }