Project Reactor:ConnectableFlux按需自动连接

时间:2019-06-16 12:48:37

标签: java reactive-programming project-reactor reactive-streams

我只有一个数据项源,我想与多个下游流共享该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),但未成功)

我怀疑我的两个问题联系在一起,因为对一个问题的回答将有助于另一个问题。

1 个答案:

答案 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个元素安全地放入内存中,但不能再增加其他内容,那么这可以为您提供最大的时间,使订阅者可以安全地连接。