考虑以下代码:
AtomicInteger counter1 = new AtomicInteger();
AtomicInteger counter2 = new AtomicInteger();
Flux<Object> source = Flux.generate(emitter -> {
emitter.next("item");
});
Executor executor1 = Executors.newFixedThreadPool(32);
Executor executor2 = Executors.newFixedThreadPool(32);
Flux<String> flux1 = Flux.merge(source).concatMap(item -> Mono.fromCallable(() -> {
Thread.sleep(1);
return "1_" + counter1.incrementAndGet();
}).subscribeOn(Schedulers.fromExecutor(executor1)));
Flux<String> flux2 = Flux.merge(source).concatMap(item -> Mono.fromCallable(() -> {
Thread.sleep(100);
return "2_" + counter2.incrementAndGet();
}).subscribeOn(Schedulers.fromExecutor(executor2)));
Flux.merge(flux1, flux2).subscribe(System.out::println);
您会看到一个发布者的速度是另一发布者的100倍。尽管如此,运行代码时似乎所有数据都已打印,但是两个发布者之间仍然存在巨大差距,这会增加超时时间。
值得注意的是,在更改数字时,executer2
将具有1024
个线程,而executer1
将仅具有1
个线程,那么我们仍然看到了差距加班越来越大。
我的期望是,在相应地调整了线程池之后,发布者将变得平衡。
我想在发布者之间取得平衡(相对于线程池大小和处理时间)
如果我等待足够长的时间会发生什么?换句话说,会产生背压吗? (默认情况下,我认为这是运行时异常,对吗?)
我不想删除项目,也不想有运行时异常。相反,如上所述,我希望系统在资源和处理时间方面达到平衡-上面的代码是否可以保证?
谢谢!
答案 0 :(得分:1)
在此示例中,您的Flux
对象不是ParallelFlux
对象,因此它们只能使用一个线程。
是否创建能够处理数千个线程的调度程序并将其传递给Flux
对象之一都没关系-它们只是闲置在那里,这正是发生在其中的事情这个例子。没有背压,也不会导致异常-它只使用一个线程就可以运行得最快。
如果要确保Flux
充分利用了可用的1024个线程,则需要调用.parallel(1024)
:
ParallelFlux<String> flux1 = Flux.merge(source).parallel(1).concatMap(item -> Mono.fromCallable(() -> {
Thread.sleep(1);
return "1_" + counter1.incrementAndGet();
}).subscribeOn(Schedulers.fromExecutor(executor1)));
ParallelFlux<String> flux2 = Flux.merge(source).parallel(1024).concatMap(item -> Mono.fromCallable(() -> {
Thread.sleep(100);
return "2_" + counter2.incrementAndGet();
}).subscribeOn(Schedulers.fromExecutor(executor1)));
如果您对代码执行此操作,则2_
航行经过1_
,尽管事实上它的睡眠时间是原来的100倍,但您开始看到的结果却与预期的结果更加接近:
...
2_17075
2_17076
1_863
1_864
2_17077
1_865
2_17078
2_17079
...
但是,请注意:
我想在发布者之间取得平衡(相对于线程池大小和处理时间)
您不能在这里选择数字来平衡输出,至少不能可靠地或以任何有意义的方式进行-线程调度将完全是任意的。如果要这样做,则可以使用this variant of the subscribe method,从而允许您在订阅使用者上显式调用request()
。然后,您可以通过仅请求准备处理的元素来提供背压。