web-dev-qa-db-ja.com

Project Reactor:ConnectableFlux自動オンデマンド接続

データアイテムの単一のソースがあり、そのFluxを複数のダウンストリームストリームと共有したい。

これは リファレンスガイドの例 に非常に似ていますが、.connect()を手動で呼び出すと、この例はだまされてしまいます。具体的には、ダウンストリームサブスクライバーの数がわかりません。また、[最後に] .connect()を呼び出すコントロールがありません。消費者はサブスクライブできる必要がありますが、すぐにデータのプルをトリガーしないでください。そして、データが実際に必要になる将来のどこかで、必要に応じてプルします。

さらに、ソースは消費量の影響を受けやすいため、再フェッチできません。
これに追加すると、非常に大きくなるため、バッファリングと再生はオプションではありません。

理想的には、これらすべてに加えて、すべてが1つのスレッドで行われるため、同時実行性や待機はありません。
(加入者が参加するのに非常に短い待ち時間を与えることは望ましくありません)

Monos(単一の最終結果値)にほぼ望ましい効果を達成することができました。

_public class CoConsumptionTest {
    @Test
    public void convenientCoConsumption() {
        // List used just for the example:
        List<Tuple2<String, String>> source = Arrays.asList(
                Tuples.of("a", "1"), Tuples.of("b", "1"), Tuples.of("c", "1"),
                Tuples.of("a", "2"), Tuples.of("b", "2"), Tuples.of("c", "2"),
                Tuples.of("a", "3"), Tuples.of("b", "3"), Tuples.of("c", "3")
        );

        // Source which is sensitive to consumption
        AtomicInteger consumedCount = new AtomicInteger(0);
        Iterator<Tuple2<String, String>> statefulIterator = new Iterator<Tuple2<String, String>>() {
            private ListIterator<Tuple2<String, String>> sourceIterator = source.listIterator();

            @Override
            public boolean hasNext() {
                return sourceIterator.hasNext();
            }

            @Override
            public Tuple2<String, String> next() {
                Tuple2<String, String> e = sourceIterator.next();
                consumedCount.incrementAndGet();
                System.out.println("Audit: " + e);
                return e;
            }
        };

        // Logic in the service:
        Flux<Tuple2<String, String>> f = Flux.fromIterable(() -> statefulIterator);
        ConnectableFlux<Tuple2<String, String>> co = f.publish();

        Function<Predicate<Tuple2<String, String>>, Mono<Tuple2<String, String>>> findOne = (highlySelectivePredicate) ->
                co.filter(highlySelectivePredicate)
                        .next() //gives us a Mono
                        .toProcessor() //makes it eagerly subscribe and demand from the upstream, so it wont miss emissions
                        .doOnSubscribe(s -> co.connect()); //when an actual user consumer subscribes

        // Subscribing (outside the service)
        assumeThat(consumedCount).hasValue(0);
        Mono<Tuple2<String, String>> a2 = findOne.apply(select("a", "2"));
        Mono<Tuple2<String, String>> b1 = findOne.apply(select("b", "1"));
        Mono<Tuple2<String, String>> c1 = findOne.apply(select("c", "1"));
        assertThat(consumedCount).hasValue(0);

        // Data is needed
        SoftAssertions softly = new SoftAssertions();

        assertThat(a2.block()).isEqualTo(Tuples.of("a", "2"));
        softly.assertThat(consumedCount).hasValue(4);

        assertThat(b1.block()).isEqualTo(Tuples.of("b", "1"));
        softly.assertThat(consumedCount).hasValue(4);

        assertThat(c1.block()).isEqualTo(Tuples.of("c", "1"));
        softly.assertThat(consumedCount).hasValue(4);

        softly.assertAll();
    }

    private static Predicate<Tuple2<String, String>> select(String t1, String t2) {
        return e -> e.getT1().equals(t1) && e.getT2().equals(t2);
    }
}
_

Question:Fluxの結果、つまり、最初/次だけではなく、フィルタリングが適用された後の複数の値に対してこれを達成する方法を知りたいです。 (それでも必要な分だけ要求する)
.toProcessor().publish().autoConnect(0)で単純に置き換えてみましたが、成功しませんでした)

編集1:ソースのバッファリングは許可されていませんが、パラメータとして提供されるフィルタは高度に選択的であることが期待されるため、フィルタリング後のバッファリングは問題ありません。

編集2:しばらくしてからこれに戻って、投稿したサンプルを新しいバージョンのreactorで試しましたが、実際に機能します。

_io.projectreactor:reactor-bom:Californium-SR8
> io.projectreactor:reactor-core:3.2.9.RELEASE
_
6
Anly

「非回答」スタイルの回答をするのは好きではありませんが、少なくとも1つ要件をここに指定する必要があります。あなたの質問から、要件は次のようです:

  • バッファリングは許可されていません
  • 要素をドロップすることはできません
  • 不明な数の加入者
  • 加入者はいつでも接続できます
  • 各サブスクライバーは、必要なときにすべてのデータを利用できる必要があります
  • ソースからの再フェッチなし

あるサブスクライバーがFluxからのデータを要求すると、そのFluxの最初のいくつかの要素が消費され、最終的に別のサブスクライバーが同じことを望む将来の任意のポイントに表示されます。データ。上記の要件では、それは不可能です。データを再度取得するか、どこかに保存する必要があり、両方のオプションを除外しました。

ただし、これらの要件を少し緩和する準備ができている場合は、いくつかの潜在的なオプションがあります。

既知の加入者数

どういうわけか最終的に取得するサブスクライバーの数を計算できる場合は、その数のサブスクリプションが行われた後、autoConnect(n)を使用してConnectableFluxに自動的に接続できます。

要素を削除できるようにする

要素の削除を許可できる場合は、元のFluxshare();を呼び出すだけで、最初のサブスクリプションで自動接続し、その後のサブスクライバーは以前の要素を持つことができます。落とした。

加入者が接続するための時間を確保する

次のように言うので、これはおそらく最も有望な戦略の1つです。

同時実行も待機もありません。 (加入者が参加するのに非常に短い待機時間を与えることは望ましくありません)

Fluxを特定の期間にすべての放出された要素をキャッシュするホットソースに変換できます。 これは、ある程度のメモリを犠牲にして(ただし、バッファリングなしで)できることを意味します。ストリーム全体)、サブスクライバーがサブスクライブしてすべてのデータを受信できるようになるまでの待ち時間を短くします。

既知の数の要素のバッファリング

上記と同様に、 cache()メソッドの別のバリアント を使用して、既知の数をキャッシュするだけです要素の。 n要素をメモリに安全に収めることができるとわかっていても、それ以上はできない場合は、サブスクライバーが安全に接続できる最大時間を確保できます。

1
Michael Berry