データアイテムの単一のソースがあり、その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
_
「非回答」スタイルの回答をするのは好きではありませんが、少なくとも1つ要件をここに指定する必要があります。あなたの質問から、要件は次のようです:
あるサブスクライバーがFlux
からのデータを要求すると、そのFlux
の最初のいくつかの要素が消費され、最終的に別のサブスクライバーが同じことを望む将来の任意のポイントに表示されます。データ。上記の要件では、それは不可能です。データを再度取得するか、どこかに保存する必要があり、両方のオプションを除外しました。
ただし、これらの要件を少し緩和する準備ができている場合は、いくつかの潜在的なオプションがあります。
どういうわけか最終的に取得するサブスクライバーの数を計算できる場合は、その数のサブスクリプションが行われた後、autoConnect(n)
を使用してConnectableFlux
に自動的に接続できます。
要素の削除を許可できる場合は、元のFlux
でshare();
を呼び出すだけで、最初のサブスクリプションで自動接続し、その後のサブスクライバーは以前の要素を持つことができます。落とした。
次のように言うので、これはおそらく最も有望な戦略の1つです。
同時実行も待機もありません。 (加入者が参加するのに非常に短い待機時間を与えることは望ましくありません)
Flux
を特定の期間にすべての放出された要素をキャッシュするホットソースに変換できます。 これは、ある程度のメモリを犠牲にして(ただし、バッファリングなしで)できることを意味します。ストリーム全体)、サブスクライバーがサブスクライブしてすべてのデータを受信できるようになるまでの待ち時間を短くします。
上記と同様に、 cache()
メソッドの別のバリアント を使用して、既知の数をキャッシュするだけです要素の。 n
要素をメモリに安全に収めることができるとわかっていても、それ以上はできない場合は、サブスクライバーが安全に接続できる最大時間を確保できます。