当主题具有多个分区时,KTable-KTable外键联接不会生成所有消息

时间:2020-07-13 21:04:15

标签: java spring-boot apache-kafka apache-kafka-streams spring-cloud-stream

请参阅下面的更新以显示可能的解决方法

我们的应用程序将2个主题用作KTables,执行左联接,然后输出到一个主题。在测试过程中,我们发现当我们的输出主题只有1个分区时,这可以按预期工作。当我们增加分区数量时,我们注意到输出主题产生的消息数量减少了。

在启动应用程序之前,我们使用多种分区配置对这一理论进行了测试。使用1个分区,我们可以看到100%的消息。使用2,我们会看到一些消息(少于50%)。有了10,我们几乎看不到任何东西(少于10%)。

由于我们处于退出状态,因此从主题1消耗的每条消息都应写入到我们的输出主题中,但是我们发现这没有发生。似乎消息被卡在通过Ktables外键联接创建的“中间”主题中,但是没有错误消息。

任何帮助将不胜感激!

Service.java

@Bean
public BiFunction<KTable<MyKey, MyValue>, KTable<MyOtherKey, MyOtherValue>, KStream<MyKey, MyEnrichedValue>> process() {

    return (topicOne, topicTwo) ->
            topicOne
                    .leftJoin(topicTwo,
                            value -> MyOtherKey.newBuilder()
                                    .setFieldA(value.getFieldA())
                                    .setFieldB(value.getFieldB())
                                    .build(),
                            this::enrich)
                    .toStream();
}

build.gradle

plugins {
    id 'org.springframework.boot' version '2.3.1.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'com.commercehub.gradle.plugin.avro' version '0.9.1'
}

...

ext {
    set('springCloudVersion', "Hoxton.SR6")
}

...

implementation 'org.springframework.cloud:spring-cloud-stream-binder-kafka-streams'
implementation 'io.confluent:kafka-streams-avro-serde:5.5.1'

注意:由于spring-cloud-stream中包含的版本中的错误,我们排除了org.apache.kafka依赖项

application.yml

spring:
  application:
    name: app-name
    stream:
      bindings:
        process-in-0:
          destination: topic1
          group: ${spring.application.name}
        process-in-1:
          destination: topic2
          group: ${spring.application.name}
        process-out-0:
          destination: outputTopic
      kafka:
        streams:
          binder:
            applicationId: ${spring.application.name}
            brokers: ${KAFKA_BROKERS}
            configuration:
              commit.interval.ms: 1000
              producer:
                acks: all
                retries: 20
              default:
                key:
                  serde: io.confluent.kafka.streams.serdes.avro.SpecificAvroSerde
                value:
                  serde: io.confluent.kafka.streams.serdes.avro.SpecificAvroSerde
            min-partition-count: 2

测试场景:

作为一个具体的例子,如果我将以下3条消息发布到主题1:

{"fieldA": 1, "fieldB": 1},,{"fieldA": 1, "fieldB": 1}
{"fieldA": 2, "fieldB": 2},,{"fieldA": 2, "fieldB": 2}
{"fieldA": 3, "fieldB": 3},,{"fieldA": 3, "fieldB": 3}
{"fieldA": 4, "fieldB": 4},,{"fieldA": 4, "fieldB": 4}

输出主题将仅收到2条消息。

{"fieldA": 2, "fieldB": 2},,{"fieldA": 2, "fieldB": 2}
{"fieldA": 3, "fieldB": 3},,{"fieldA": 3, "fieldB": 3}

其他2发生了什么?似乎某些键/值对无法写入输出主题。重试这些“丢失”的消息也不起作用。

更新

通过将主题1用作KStream而不是KTable,并在继续进行KTable-KTable连接之前调用toTable(),我能够使此功能正常运行。我仍然不确定为什么我的原始解决方案不起作用,但是希望这种解决方法可以对实际问题有所启发。

@Bean
public BiFunction<KStream<MyKey, MyValue>, KTable<MyOtherKey, MyOtherValue>, KStream<MyKey, MyEnrichedValue>> process() {

    return (topicOne, topicTwo) ->
            topicOne
                    .map(...)
                    .toTable()
                    .leftJoin(topicTwo,
                            value -> MyOtherKey.newBuilder()
                                    .setFieldA(value.getFieldA())
                                    .setFieldB(value.getFieldB())
                                    .build(),
                            this::enrich)
                    .toStream();
}

3 个答案:

答案 0 :(得分:1)

给出问题的描述,似乎(左)KTable输入主题中的数据未正确地由其键分区。好吧,对于一个分区的主题,只有一个分区,并且所有数据都转到该一个分区,并且联接结果是完整的。

但是,对于多分区的输入主题,您需要确保按键对数据进行分区,否则,具有相同键的两条记录可能会以不同的分区结尾,因此联接失败(因为联接已完成) (按分区)。

请注意,即使外键联接不需要两个输入主题都被共同分区,仍然需要每个输入主题本身都被其键分区!

如果您使用map().toTable(),则基本上会触发数据的内部重新分区,以确保通过密钥对数据进行分区,从而解决了问题。

答案 1 :(得分:-1)

选择已加入主题的键可能会有所帮助。主题的分区配置应该相同。

return (topicOne, topicTwo) ->
        topicOne
            .leftJoin(topicTwo,
                value -> MyOtherKey.newBuilder()
                    .setFieldA(value.getFieldA())
                    .setFieldB(value.getFieldB())
                    .build(),
                this::enrich)
            .toStream().selectKey((key, value) -> key);

答案 2 :(得分:-1)

这是一个奇怪的问题,我从未听说过许多控制数据写入频率的输出主题分区。但是我确实知道toStream()仅在缓存已满时才将数据写入下游,因此请尝试设置cache.max.bytes.buffering = 0。 另外,KTable仅保留每个键的最新记录,因此,如果您对同一个键有多个值,则只会保留最新值并向下游写入。