我只有一个数据项源,我想与多个下游流共享该Flux。
它与the example in the reference guide非常相似,
但我觉得该示例通过手动调用.connect()
作弊。
具体来说,我不知道会有多少下游订户,并且我没有控制权“最后”调用.connect()
。
消费者应该能够订阅,但不能立即触发数据提取。然后在将来实际需要数据的某个地方,它们将根据需要拉出。
此外,源对消耗很敏感,因此无法重新获取。
除此之外,它会很大,因此不能选择缓冲和重播。
理想地,最重要的是,整个过程发生在一个线程中,因此无需并发或等待。
(不希望为订阅者提供非常小的等待时间)
我能够达到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();
BiFunction<String, String, Mono<Tuple2<String, String>>> findOne = (t1, t2) ->
co.filter(e -> e.getT1().equals(t1) && e.getT2().equals(t2))
.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("a", "2");
Mono<Tuple2<String, String>> b1 = findOne.apply("b", "1");
Mono<Tuple2<String, String>> c1 = findOne.apply("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); //fails
assertThat(b1.block()).isEqualTo(Tuples.of("b", "1"));
softly.assertThat(consumedCount).hasValue(4); //fails
assertThat(c1.block()).isEqualTo(Tuples.of("c", "1"));
softly.assertThat(consumedCount).hasValue(4); //fails
softly.assertAll();
}
}
第一季度:我想知道如何控制需求,而不是急于要求所有。 当前的实现要求无限制的数量,并使整个源一次性消耗掉。 查看失败的(软)断言。
第二季度:另外,我想知道如何针对Flux结果实现此目标,即在应用过滤后针对多个值,而不仅仅是第一个/下一个。 (仍然要求尽可能多的要求)
(尝试将.toProcessor()
替换为.publish().autoConnect(0)
,但未成功)
我怀疑我的两个问题联系在一起,因为对一个问题的回答将有助于另一个问题。
答案 0 :(得分:0)
我不喜欢给出“非回答”样式的答案,但是我认为至少需要在这里给出一个答案。从您的问题来看,要求似乎是:
以一个订户从Flux
请求数据的情况为例,Flux
中的前几个元素被消耗掉,然后最终另一个订户出现在将来想要的那个任意点相同的数据。有了上述要求,这是不可能的-您要么不得不再次获取数据,要么将其保存在某个位置,然后就排除了这两种选择。
但是,如果您准备稍微放松一下这些要求,那么有一些潜在的选择:
如果您可以算出最终的订阅者数量,则可以在完成该数量的订阅后使用autoConnect(n)
自动连接到ConnectableFlux
。
如果您允许删除元素,则只需在原始share();
上调用Flux
,使其在第一个订阅上自动连接,然后以后的订阅者将拥有先前的元素放下。
这也许是最有前途的策略之一,因为您说过:
没有并发或等待。 (不希望为订阅者提供非常小的等待时间)
You can turn the Flux
into a hot source that caches all emitted elements for a certain time period.这意味着您可以以一定数量的内存(但不缓冲整个流)为代价,为订户提供一个小的等待时间,以便他们可以订阅并仍然接收所有数据。
与上述类似,您可以使用another variant of the cache()
method来缓存已知数量的个元素。如果您知道可以将n
个元素安全地放入内存中,但不能再增加其他内容,那么这可以为您提供最大的时间,使订阅者可以安全地连接。