Apache kafka 多个StreamListener,Spring云流连接到Kafka

Apache kafka 多个StreamListener,Spring云流连接到Kafka,apache-kafka,spring-cloud-stream,spring-kafka,Apache Kafka,Spring Cloud Stream,Spring Kafka,在使用连接到Kafka的Spring Cloud Stream的Spring Boot应用程序中,我尝试设置两个单独的流侦听器方法: 其中一个将主题“t1”和“t2”作为ktable读取,在其中一个中使用不同的键重新分区,然后连接到另一个中的数据 另一个从一个无关的主题“t3”中读取,作为KStream 因为第一个侦听器执行一些连接和聚合,所以会自动创建一些主题,例如“test-1-KTABLE-AGGREGATE-STATE-STORE-000000000 7-repartition-0”

在使用连接到Kafka的Spring Cloud Stream的Spring Boot应用程序中,我尝试设置两个单独的流侦听器方法:

  • 其中一个将主题“t1”和“t2”作为ktable读取,在其中一个中使用不同的键重新分区,然后连接到另一个中的数据
  • 另一个从一个无关的主题“t3”中读取,作为KStream
因为第一个侦听器执行一些连接和聚合,所以会自动创建一些主题,例如“test-1-KTABLE-AGGREGATE-STATE-STORE-000000000 7-repartition-0”。(不确定这是否与问题有关。)

当我通过使用
@StreamListener
注释两个单独的方法来设置代码时,当Spring Boot应用程序启动时,我会出现以下错误:

Exception in thread "test-d44cb424-7575-4f5f-b148-afad034c93f4-StreamThread-2" java.lang.IllegalArgumentException: Assigned partition t1-0 for non-subscribed topic regex pattern; subscription pattern is t3
    at org.apache.kafka.clients.consumer.internals.SubscriptionState.assignFromSubscribed(SubscriptionState.java:195)
    at org.apache.kafka.clients.consumer.internals.ConsumerCoordinator.onJoinComplete(ConsumerCoordinator.java:225)
    at org.apache.kafka.clients.consumer.internals.AbstractCoordinator.joinGroupIfNeeded(AbstractCoordinator.java:367)
    at org.apache.kafka.clients.consumer.internals.AbstractCoordinator.ensureActiveGroup(AbstractCoordinator.java:316)
    at org.apache.kafka.clients.consumer.internals.ConsumerCoordinator.poll(ConsumerCoordinator.java:295)
    at org.apache.kafka.clients.consumer.KafkaConsumer.pollOnce(KafkaConsumer.java:1146)
    at org.apache.kafka.clients.consumer.KafkaConsumer.poll(KafkaConsumer.java:1111)
    at org.apache.kafka.streams.processor.internals.StreamThread.pollRequests(StreamThread.java:848)
    at org.apache.kafka.streams.processor.internals.StreamThread.runOnce(StreamThread.java:805)
    at org.apache.kafka.streams.processor.internals.StreamThread.runLoop(StreamThread.java:771)
    at org.apache.kafka.streams.processor.internals.StreamThread.run(StreamThread.java:741)
我认为重要的部分是:“为未订阅的主题正则表达式模式分配分区t1-0;订阅模式是t3”。这是两个不相关的主题,所以据我所知,与t3相关的内容都不应该订阅与t1相关的内容。导致问题的确切主题也会断断续续地发生变化:有时会提到自动生成的主题之一,而不是t1本身

以下是如何设置两个流侦听器(在Kotlin中):

但是我认为我只能有一个
@StreamListener
方法是不对的

我知道可以为
StreamListener
注释添加条件,但是如果方法定义了输入通道,那么我不确定是否需要在这里使用它-我认为在方法参数上使用
@input
注释足以告诉系统哪些通道(因此,哪些卡夫卡主题)要绑定?如果我是否需要使用基于内容的路由,我如何在此处应用它,使每个方法仅接收来自相关主题的项目

我还尝试将两个侦听器方法分离为两个单独的类,每个类都有
@EnableBinding
,仅用于它感兴趣的接口(即,一个接口用于t1和t2,另一个接口用于t3),但这没有帮助


我发现的与此错误消息相关的所有其他内容,例如,关于有多个应用程序实例,但在我的例子中,只有一个Spring Boot应用程序实例。

每个
StreamListener
方法都需要单独的应用程序id。以下是一个示例:


spring.cloud.stream.kafka.streams.bindings.t1.consumer.application id=processor1应用程序id
spring.cloud.stream.kafka.streams.bindings.t2.consumer.application id=processor1应用程序id
spring.cloud.stream.kafka.streams.bindings.t3.consumer.application id=processor2应用程序id

您可能希望使用最新的快照(2.1.0)进行测试,因为活页夹处理应用程序id的方式最近发生了一些更改

有关更多详细信息,请参阅更新。
下面是一组属于Kafka Streams处理器的多个
StreamListener
方法。

每个
StreamListener
方法都需要单独的应用程序id。下面是一个示例:


spring.cloud.stream.kafka.streams.bindings.t1.consumer.application id=processor1应用程序id
spring.cloud.stream.kafka.streams.bindings.t2.consumer.application id=processor1应用程序id
spring.cloud.stream.kafka.streams.bindings.t3.consumer.application id=processor2应用程序id

您可能希望使用最新的快照(2.1.0)进行测试,因为活页夹处理应用程序id的方式最近发生了一些更改

有关更多详细信息,请参阅更新。
这里有一个多个
StreamListener
方法的列表,它们是Kafka Streams处理器。

感谢Soby对其进行了排序。你说得对,我必须升级到最新的快照版本:如果不这样做,上面的更改不会产生影响。在Streams 3.0.6中,绑定需要包括“-In-”,例如,
spring.cloud.stream.kafka.streams.bindings.process2-in-0.consumer.application id=process2 id
感谢Soby对其进行了排序。您是对的,我必须升级到最新的快照版本:如果不升级,上述更改不会产生任何影响。在streams 3.0.6中,绑定需要包括“-in-”,例如,
spring.cloud.stream.kafka.streams.bindings.process2-in-0.consumer.application id=process2 id
@StreamListener
fun listenerForT1AndT2(
        @Input("t1") t1KTable: KTable<String, T1Obj>,
        @Input("t2") t2KTable: KTable<String, T2Obj>) {

    t2KTable
        .groupBy(...)
        .aggregate(
                { ... },
                { ... },
                { ... },
                Materialized.with(Serdes.String(), JsonSerde(SomeObj::class.java)))
        .join(t1KTable,
                { ... },
                Materialized.`as`<String, SomeObj, KeyValueStore<Bytes, ByteArray>>("test")
                        .withKeySerde(Serdes.String())
                        .withValueSerde(JsonSerde(SomeObj::class.java)))
}

@StreamListener
fun listenerForT3(@Input("t3") t3KStream: KStream<String, T3Obj>) {
    events.map { ... }
}
@StreamListener
fun compositeListener(
        @Input("t1") t1KTable: KTable<String, T1Obj>,
        @Input("t2") t2KTable: KTable<String, T2Obj>,
        @Input("t3") t3KStream: KStream<String, T3Obj>) {
    ...
}