[英]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 中創建的:
我希望這個文件夾會在優雅的單元測試退出時自動刪除?
這可能是 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.