Combine Mono with Flux
我想创建一个将两个反应性来源的结果结合起来的服务。
一个正在生产Mono,另一个正在生产Flux。对于合并,我需要为每个发射的通量使用相同的mono值。
现在我有这样的东西
1 2 3 4 5 | Flux.zip( service1.getConfig(), //produces flux service2.getContext() //produces mono .cache().repeat() ) |
这给了我我需要的东西,
- service2仅被调用一次
- 为每个配置提供了上下文
- 产生的助焊剂具有与配置一样多的元素
但是我注意到缓存上下文后,repeat()发出了大量元素。这有问题吗?
是否可以做些限制重复次数的配置,但仍然同时请求?
还是这不是问题,我可以安全地忽略那些额外发出的元素吗?
我尝试使用
编辑
参考@Ricard Kollcaku的建议,我创建了示例测试,以显示为什么这不是我想要的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Stream; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; import reactor.test.StepVerifier; public class SampleTest { Logger LOG = LoggerFactory.getLogger(SampleTest.class); AtomicLong counter = new AtomicLong(0); Flux<String> getFlux() { return Flux.fromStream(() -> { LOG.info("flux started"); sleep(1000); return Stream.of("a","b","c"); }).subscribeOn(Schedulers.parallel()); } Mono<String> getMono() { return Mono.defer(() -> { counter.incrementAndGet(); LOG.info("mono started"); sleep(1000); return Mono.just("mono"); }).subscribeOn(Schedulers.parallel()); } private void sleep(final long milis) { try { Thread.sleep(milis); } catch (final InterruptedException e) { e.printStackTrace(); } } @Test void test0() { final Flux<String> result = Flux.zip( getFlux(), getMono().cache().repeat() .doOnNext(n -> LOG.warn("signal on mono", n)), (s1, s2) -> s1 +"" + s2 ); assertResults(result); } @Test void test1() { final Flux<String> result = getFlux().flatMap(s -> Mono.zip(Mono.just(s), getMono(), (s1, s2) -> s1 +"" + s2)); assertResults(result); } @Test void test2() { final Flux<String> result = getFlux().flatMap(s -> getMono().map((s1 -> s +"" + s1))); assertResults(result); } void assertResults(final Flux<String> result) { final Flux<String> flux = result; StepVerifier.create(flux) .expectNext("a mono") .expectNext("b mono") .expectNext("c mono") .verifyComplete(); Assertions.assertEquals(1L, counter.get()); } |
查看test1和test2的测试结果
1 2 3 4 5 6 | 2020-01-20 12:55:22.542 INFO [] [] [ parallel-3] SampleTest : flux started 2020-01-20 12:55:24.547 INFO [] [] [ parallel-4] SampleTest : mono started 2020-01-20 12:55:24.547 INFO [] [] [ parallel-5] SampleTest : mono started 2020-01-20 12:55:24.548 INFO [] [] [ parallel-6] SampleTest : mono started expected: <1> but was: <3> |
我需要拒绝你的建议。在这两种情况下,getMono都是
-调用与flux中的项目一样多的次数
-在磁通量的第一个元素到达后??调用
这些是我要避免的互动。我的服务正在发出HTTP请求,这可能很耗时。
我当前的解决方案没有这个问题,但是如果我将记录器添加到我的zip中,我会得到这个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 2020-01-20 12:55:20.505 INFO [] [] [ parallel-1] SampleTest : flux started 2020-01-20 12:55:20.508 INFO [] [] [ parallel-2] SampleTest : mono started 2020-01-20 12:55:21.523 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.528 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.529 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.529 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.529 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.529 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.530 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.530 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.530 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.530 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.531 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.531 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.531 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.531 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.531 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.532 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.532 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.532 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.532 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.533 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.533 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.533 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.533 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.533 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.533 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.533 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.534 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.534 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.534 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.534 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.534 WARN [] [] [ parallel-2] SampleTest : signal on mono 2020-01-20 12:55:21.535 WARN [] [] [ parallel-2] SampleTest : signal on mono |
如您所见,将
我认为您要实现的目标可以通过
完成
这是一些示例代码:
1 2 3 4 5 6 7 8 9 10 | Flux<Integer> flux = Flux.concat(Mono.just(1).delayElement(Duration.ofMillis(100)), Mono.just(2).delayElement(Duration.ofMillis(500))).log(); Mono<String> mono = Mono.just("a").delayElement(Duration.ofMillis(50)).log(); List<String> list = flux.join(mono, (v1) -> Flux.never(), (v2) -> Flux.never(), (x, y) -> { return x + y; }).collectList().block(); System.out.println(list); |
只需一个简单的更改即可完成
1 2 3 4 5 6 7 8 9 10 | getFlux() .flatMap(s -> Mono.zip(Mono.just(s),getMono(), (s1, s2) -> s1+""+s2)) .subscribe(System.out::println); Flux<String> getFlux(){ return Flux.just("a","b","c"); } Mono<String> getMono(){ return Mono.just("mono"); } |
如果要使用zip,但可以使用flatmap
获得相同的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 | getFlux() .flatMap(s -> getMono() .map((s1 -> s +"" + s1))) .subscribe(System.out::println); } Flux<String> getFlux() { return Flux.just("a","b","c"); } Mono<String> getMono() { return Mono.just("mono"); } |
在两个结果中都是:
单声道
b单声道
c mono
编辑
好的,现在我更了解了。您可以尝试此解决方案吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | getMono(). flatMapMany(s -> getFlux().map(s1 -> s1 +"" + s)) .subscribe(System.out::println); Flux<String> getFlux() { return Flux.defer(() -> { System.out.println("init flux"); return Flux.just("a","b","c"); }); } Mono<String> getMono() { return Mono.defer(() -> { System.out.println("init Mono"); return Mono.just("sss"); }); } |
像Project Reactor和RxJava这样的库试图提供它们功能的尽可能多的组合,但是不提供对组合功能的工具的访问。结果,总是有一些未涵盖的极端情况。
据我所知,我自己的DF4J是唯一提供组合功能的异步库。例如,这是用户压缩Flux和Mono的方式:(当然,此类不是DF4J本身的一部分):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | import org.df4j.core.dataflow.Actor; import org.df4j.core.port.InpFlow; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; abstract class ZipActor<T1, T2> extends Actor { InpFlow<T1> inpFlow = new InpFlow<>(this); InpFlow<T2> inpScalar = new InpFlow<>(this); ZipActor(Flux<T1> flux, Mono<T2> mono) { flux.subscribe(inpFlow); mono.subscribe(inpScalar); } @Override protected void runAction() throws Throwable { if (inpFlow.isCompleted()) { stop(); return; } T1 element1 = inpFlow.removeAndRequest(); T2 element2 = inpScalar.current(); runAction(element1, element2); } protected abstract void runAction(T1 element1, T2 element2); } |
这是可以使用的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Test public void ZipActorTest() { Flux<Integer> flux = Flux.just(1,2,3); Mono<Integer> mono = Mono.just(5); ZipActor<Integer, Integer> actor = new ZipActor<Integer, Integer>(flux, mono){ @Override protected void runAction(Integer element1, Integer element2) { System.out.println("got:"+element1+" and:"+element2); } }; actor.start(); actor.join(); } |
控制台输出如下:
1 2 3 | got:1 and:5 got:2 and:5 got:3 and:5 |