【问题标题】:Spring Cloud Stream Kafka Streams: The number of downstream messages doesn't match the sum of messages sent to the topicSpring Cloud Stream Kafka Streams:下游消息数与发送到主题的消息数不匹配
【发布时间】:2020-09-18 01:52:41
【问题描述】:

我有一个基于 Spring Boot 的 Spring Cloud Stream Kafka Streams Binder 应用程序。 它定义了一个拓扑,其中包含以下部分:

绿色数字表示通过 Spring Cloud Stream Kafka Streams binder 绑定的各个处理器定义的拓扑传递的消息数量,以下是各自的属性:

spring.cloud.stream.bindings:
  ...
  hint1Stream-out-0:
    destination: hints
  realityStream-out-0:
    destination: hints
  countStream-in-0:
    destination: hints

我正在计算每个处理器使用peek() 方法产生/消耗的消息,如下所示:

return stream -> {
    stream
        .peek((k, v)-> input0count.incrementAndGet())
        ...
        .peek((k, v)-> output0count.incrementAndGet())
};

我正在使用具有几乎默认设置的嵌入式 Kafka 从单元测试开始我的应用程序:

@RunWith(SpringRunner.class)
@SpringBootTest(
    properties = "spring.cloud.stream.kafka.binder.brokers=${spring.embedded.kafka.brokers}"
)
@EmbeddedKafka(partitions = 1,
        topics = {
                ...
                TOPIC_HINTS
        }
)
public class MyApplicationTests {
...

在我的测试中,我等待了足够长的时间,直到所有发布的测试消息都到达 countStream:

CountDownLatch latch = new CountDownLatch(1);
...
publishFromCsv(...)
...
latch.await(30, TimeUnit.SECONDS);
logCounters();

如您所见,放入“hints”主题的消息总和与“counterStream”端的消息计数不匹配:1309 + 2589 != 3786

我可能缺少一些 Kafka 或 Kafka Streams 设置来刷新每批?也许我的自定义 TimestampExtractor 会生成“太旧”的时间戳? (我很确定它们不小于零)也许这与 Kafka 日志压缩有关?

这种不匹配的原因可能是什么?

更新

通过执行检查底层主题偏移量

kafka-run-class kafka.tools.GetOffsetShell --broker-list localhost:60231 --topic hints

当测试等待超时时。

正如预期的那样,主题中的消息数等于两个输入流计数的总和。传递到 counterStream 输入的消息数量仍然比预期的少几十个。

正在使用的其他 Kafka 配置:

spring.cloud.stream.kafka.streams:
    configuration:
      schema.registry.url: mock://torpedo-stream-registry
      default.key.serde: org.apache.kafka.common.serialization.Serdes$StringSerde
      default.value.serde: io.confluent.kafka.streams.serdes.avro.SpecificAvroSerde
      commit.interval.ms: 100

对应于processing.guarantee = at_least_once。无法测试 processing.guarantee = exactly_once,因为这需要至少有 3 个可用代理的集群。

同时设置:

spring.cloud.stream.kafka.binder.configuration:
  auto.offset.reset: earliest
spring.cloud.stream.kafka.streams.binder.configuration:
  auto.offset.reset: earliest
spring.cloud.stream.kafka.streams:
  default:
    consumer:
      startOffset: earliest
spring.cloud.stream.bindings:
  countStream-in-0:
    destination: hints
    consumer:
      startOffset: earliest
      concurrency: 1

没有帮助:(

帮助的是 stream.peak(..) 在 countStream 消费者中,例如:

@Bean
public Consumer<KStream<String, Hint>> countStream() {
    return stream -> {
        KStream<String, Hint> kstream = stream.peek((k, v) -> input0count.incrementAndGet());
    };
}

在这种情况下,我立即开始在 countConsumer 端获得预期的消息数。

这意味着我的 Count Consumer 内部对行为有影响。

这是“不起作用”的完整版:

@Bean
public Consumer<KStream<String, Hint>> countStream() {
    return stream -> {
        KStream<String, Hint> kstream = stream.peek((k, v) -> notifyObservers(input0count.incrementAndGet()));

        KStream<String, Hint> realityStream = kstream
            .filter((key, hint) -> realityDetector.getName().equals(hint.getDetector()));

        KStream<String, Hint> hintsStream = kstream
            .filter((key, hint) -> !realityDetector.getName().equals(hint.getDetector()));

        this.countsTable = kstream
            .groupBy((key, hint) -> key.concat(":").concat(hint.getDetector()))
            .count(Materialized
                .as("countsTable"));

        this.countsByActionTable = kstream
            .groupBy((key, hint) -> key.concat(":")
                .concat(hint.getDetector()).concat("|")
                .concat(hint.getHint().toString()))
            .count(Materialized
                .as("countsByActionTable"));

        this.countsByHintRealityTable = hintsStream
            .join(realityStream,
                (hint, real) -> {
                    hint.setReal(real.getHint());
                    return hint;
                }, JoinWindows.of(countStreamProperties.getJoinWindowSize()))
            .groupBy((key, hint) -> key.concat(":")
                .concat(hint.getDetector()).concat("|")
                .concat(hint.getHint().toString()).concat("-")
                .concat(hint.getReal().toString())
            )
            .count(Materialized
                .as("countsByHintRealityTable"));

    };
}

我在那里将计数存储在几个 KTable 中。这就是 Counts Consumer 内部发生的事情:

更新 2

Count Consumer 的最后一部分显然导致了最初的意外行为:

this.countsByHintRealityTable = hintsStream
        .join(realityStream,
            (hint, real) -> {
                hint.setReal(real.getHint());
                return hint;
            }, JoinWindows.of(countStreamProperties.getJoinWindowSize()))
        .groupBy((key, hint) -> key.concat(":")
            .concat(hint.getDetector()).concat("|")
            .concat(hint.getHint().toString()).concat("-")
            .concat(hint.getReal().toString())
        )
        .count(Materialized
            .as("countsByHintRealityTable"));

没有它,消息计数会按预期匹配。

这样的下游代码如何影响 Consumer KStream 输入?

【问题讨论】:

  • 想知道这是否是默认的 Kafka Streams 行为?我认为 Spring Cloud Stream binder 没有做任何事情来触发这种行为。可能想编写一个独立的 Kafka Streams 应用程序,看看你是否在那里看到同样的问题。
  • 我打算检查基础主题的偏移量,但只会在几天内完成。
  • 我想这真的取决于你的应用程序在做什么?有状态的操作员可能会“缓存和去重”对密钥的更新。消息可能格式不正确并被丢弃/跳过。一般来说,如果您没有在启用 EOS 的情况下运行并且没有正确的配置,则消息可能会被复制或丢弃。
  • 我在分别计算输出和输入,我不希望有状态转换的影响在这里发挥作用。它也是 1 个嵌入式 Kafka 实例,所有主题都具有 1 个分区,为什么 Kafka 会因为 QoS 而放弃任何东西?但这是个好建议,谢谢,我会尝试更高的 QoS 级别。
  • 格式错误的消息会导致日志中出现错误消息吗?图片上的所有消息都具有相同的密钥和 Avro 序列化内容。

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


【解决方案1】:

由于保留政策,邮件可以被删除。更改拓扑反映在更改处理所需的时间量。如果在处理过程中出现保留,您可能会丢失消息。它还取决于偏移重置策略。

尝试设置log.retention.hours=-1。这将禁用自动创建主题的保留。

【讨论】:

    【解决方案2】:

    我认为以下内容帮助了我解决这个问题:

    帮助将 Counter Consumer 拆分为两个部分(在我看来)与单个消费者实现完全等效:

    peek() 报告的两个消费者输入的消息计数显示了预期的消息数量。

    但结果是不确定的。每次下一次运行都会产生不同的结果,有时仍然不匹配。

    我发现并删除了以下在测试运行期间创建的临时文件夹:

    • /tmp/kafka-streams/*(都是空的)
    • /var/folders/ms/pqwfgz297b91gw_b8xymf1l00000gn/T/spring*(这些看起来是嵌入式 Kafka 的临时文件夹)

    在那之后,我无法用相同的代码重现该问题仍然

    我必须清理的临时目录是在 spring-kafka-test EmbeddedKafkaBroker 中创建的:

    https://github.com/spring-projects/spring-kafka/blob/master/spring-kafka-test/src/main/java/org/springframework/kafka/test/EmbeddedKafkaBroker.java#L329

    我希望这个文件夹会在优雅的单元测试退出时自动删除?

    这可能是 Kafka 本身的责任,但那里的类似错误似乎已经修复:KAFKA-1258

    我已经在

    中将 Kafka 代理 log.dir 设置为“target/kafka”

    kafka.properties

    log.dir=target/kafka
    

    MyApplicationTests.java

    @RunWith(SpringRunner.class)
    @SpringBootTest(
        properties = "spring.cloud.stream.kafka.binder.brokers=${spring.embedded.kafka.brokers}"
    )
    @EmbeddedKafka(partitions = 1,
            topics = {
                    TOPIC_QUOTES,
                    TOPIC_WINDOWS,
                    TOPIC_HINTS,
                    TOPIC_REAL
            },
            brokerPropertiesLocation = "kafka.properties"
    )
    @Slf4j
    public class MyApplicationTests {
    

    我可以看到在测试运行期间 target/kafka 文件夹是如何充满临时文件夹和文件的。它也会在测试退出时“自行”删除。

    我仍然在测试日志中看到 ${io.java.tmpdir} 中的一些文件夹正在使用中,例如/var/folders/ms/pqwfgz297b91gw_b8xymf1l00000gn/T/kafka-16220018198285185785/version-2/snapshot.0。它们也会被清理干净。

    在大多数情况下,我的计数现在匹配。不过,我想我曾经见过一次或多次他们没有。

    【讨论】:

      猜你喜欢
      • 2021-06-12
      • 2018-07-18
      • 2019-02-25
      • 2022-01-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-17
      • 2018-08-29
      相关资源
      最近更新 更多