web-dev-qa-db-ja.com

Spring Cloud Stream Kafka Streams:ダウンストリームメッセージの数がトピックに送信されたメッセージの合計と一致しません

私はSpring BootベースのSpring Cloud Stream Kafka Streams Binderアプリケーションを持っています。これは、次の要素を含むトポロジを定義します。

enter image description here

緑の数字は、Spring Cloud Stream Kafka Streamsバインダーを介してバインドされたそれぞれのプロセッサによって定義されたトポロジを介して渡されたメッセージの数を示します。それぞれのプロパティは次のとおりです。

_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())
};
_

Embedded 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();
_

ご覧のとおり、「ヒント」トピックに配置されたメッセージの合計が、「counterStream」側のメッセージの数と一致しません:_1309 + 2589 != 3786_

KafkaまたはKafkaすべてのバッチをフラッシュするためのストリーム設定が不足している?カスタムのTimestampExtractorが「古すぎる」タイムスタンプを生成するのではないか?それらはゼロ以下ではありません)多分これはKafkaログ圧縮と関係がありますか?

このミスマッチの理由は何でしょうか?

更新

実行することにより、基になるトピックのオフセットを確認しました

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

テストがタイムアウトを待っている間。

トピック内のメッセージ数は、予想どおり、2つの入力ストリーム数の合計に等しくなります。 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
_

助けにはならなかった:(

countStreamコンシューマに次のようにstream.peak(..)のみを残すのに役立ちました:

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

この場合、countConsumer側でカウントされるメッセージの予想数をすぐに取得し始めます。

つまり、私の消費者内部のカウントは行動に影響を与えます。

「動作しない」フルバージョンは次のとおりです。

_@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内で起こっていることです。

enter image description here

更新2

カウントコンシューマーの最後の部分が、初期の予期しない動作を引き起こしているようです。

_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入力にどのように影響しますか?

2

保持ポリシーにより、メッセージが削除される可能性があります。トポロジの変更は、処理に必要な時間の変更に反映されます。処理中に保持が表示される場合は、メッセージを失う可能性があります。また、オフセットリセットポリシーにも依存します。

設定してみてくださいlog.retention.hours=-1。これにより、自動作成されたトピックの保持が無効になります。

1
abinet

次のことが問題を解決するのに役立つと思いました:

助けになったのは、Counter Consumerを2つの部分に分割し(私の観点から)、単一のコンシューマーの実装と完全に同等です。

enter image description here

両方のコンシューマー入力で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。彼らも掃除されます。

ほとんどの場合、私のカウントは現在一致しています。それでも、彼らがそうでないことを何度か見たことがあります。

0