簡體   English   中英

Spring Cloud Stream Kafka Streams:下游消息的數量與發送到主題的消息的總和不匹配

[英]Spring Cloud Stream Kafka Streams: The number of downstream messages doesn't match the sum of messages sent to the topic

我有一個基於 Spring 引導的 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"));

沒有它,消息計數將按預期匹配。

這樣的下游代碼如何影響消費者 KStream 輸入?

由於保留策略,可以刪除郵件。 更改拓撲反映在更改處理所需的時間量。 如果在處理過程中出現保留,您可能會丟失消息。 它還取決於偏移重置策略。

嘗試設置log.retention.hours=-1 這將禁用自動創建主題的保留。

我認為以下內容幫助我解決了這個問題:

有幫助的是將 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 他們也得到清潔。

在大多數情況下,我的計數現在匹配。 不過,我想我見過一次或多次他們沒有。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM