具有连接到Kafka的Spring Cloud Stream的多个StreamListeners

时间:2018-10-15 09:58:09

标签: apache-kafka spring-cloud-stream spring-kafka

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

  • 一个从主题“ t1”和“ t2”中读取为KTables,使用一个不同的键重新分区,然后从另一个连接到数据
  • 另一个从不相关的主题“ t3”中读取,作为KStream。

由于第一个侦听器进行了一些合并和汇总,因此会自动创建一些主题,例如“ test-1-KTABLE-AGGREGATE-STATE-STORE-0000000007-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
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注释的方法来设置代码,并为所有三个主题获取参数时,一切正常,例如

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

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

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

我还尝试将两个侦听器方法分为两个单独的类,每个类仅对它感兴趣的接口(例如,一个用于t1和t2的接口,以及一个用于t3的接口)具有@EnableBinding ),但这无济于事。

我发现的所有与此错误消息相关的其他信息,例如here是关于拥有多个应用程序实例的,但就我而言,只有一个Spring Boot应用程序实例。

1 个答案:

答案 0 :(得分:2)

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

spring.cloud.stream.kafka.streams.bindings.t1.consumer.application-id=processor1-application-id spring.cloud.stream.kafka.streams.bindings.t2.consumer.application-id=processor1-application-id spring.cloud.stream.kafka.streams.bindings.t3.consumer.application-id=processor2-application-id

您可能想使用最新的快照(2.1.0)进行测试,因为活页夹处理应用程序ID的方式最近有所更改。

有关更多详细信息,请参见更新here。 这是多个StreamListener方法中的working sample,它们是Kafka Streams处理器。