对于完全非阻塞的端到端响应式调用,是否建议显式调用 publishOn 或 subscribeOn 来切换调度程序?对于 CPU 消耗或非消耗任务,总是使用并行通量来优化性能是否有利?
2 回答
值得说明的是,我假设这里的上下文是 Webflux 而不是一般的反应器(因为问题是这样标记的。)如果我们在不考虑 Webflux 的情况下谈论通用反应器用例,那么建议当然会有很大差异。
对于完全非阻塞的端到端响应式调用,是否建议显式调用 publishOn 或 subscribeOn 来切换调度程序?
一般建议是不要显式调用这些方法,除非您有理由这样做。(在正确的上下文中使用它们并没有错,但这样做“仅仅因为”可能不会带来任何好处。)
对于 CPU 消耗或非消耗任务,总是使用并行通量来优化性能是否有利?
这取决于您要实现的目标,以及“CPU 消耗”(或 CPU 密集型)任务的含义。请注意,我在这里谈论的是真正的CPU 密集型任务,而不是阻塞代码——在这种情况下,我理想地将 CPU 密集型部分分配给另一个微服务,使您能够根据需要进行扩展,与您的 Webflux 服务分开。
使用并行通量(并在并行调度程序上运行)应该使用所有可用的内核来处理数据——这很可能会导致处理得更快。但请记住,默认情况下,您还为每个内核运行一个事件循环,因此您基本上从事件循环中“窃取”了一些可用容量以实现这一目标。这是否理想取决于您的用例,但通常不会带来太多好处。
相反,我推荐两种方法:
- 如果您可以将 CPU 密集型任务分解为小的、低强度的块,请这样做 - 然后您可以将其保留在事件循环中。这允许事件循环保持及时运行,同时这些 CPU 密集型任务按其可能的方式安排。
- 如果您无法将其分解,请启动一个单独的调度程序(可以选择具有低优先级,因此不太可能从事件循环中窃取资源),然后将所有 CPU 密集型任务分配给该调度程序。这具有创建更多线程的缺点,但又使事件循环保持空闲。默认情况下,您将拥有与事件循环的内核一样多的线程 - 您可能希望减少线程数,以便为您的“CPU 密集型”调度程序提供更多可使用的内核。
对于完全非阻塞的端到端响应式调用,是否建议显式调用 publishOn 或 subscribeOn 来切换调度程序?
publishOn
当您将数据发布到下游时subscribeOn
使用,而当您从上游消费数据时使用。所以这真的取决于你想从事什么样的工作。
对于 CPU 消耗或非消耗任务,总是使用并行通量来优化性能是否有利?
绝对不是,考虑这个例子:
Flux.range(1, 10)
.parallel(4)
.runOn(Schedulers.parallel())
.sequential()
.elapsed()
.subscribe(i -> System.out.printf(" %s ", i));
上面的代码完全是浪费,因为i
几乎会立即处理。以下代码将比上面的代码执行得更好:
Flux.range(1, 10)
.elapsed()
.subscribe(i -> System.out.printf(" %s ", i));
现在考虑一下:
public static <T> T someMethodThatBlocks(T i, int ms) {
try { Thread.sleep( ms ); }
catch (InterruptedException e) {}
return i;
}
// some method here
Flux.range(1, 10)
.parallel(4)
.runOn(Schedulers.parallel())
.map(i -> someMethodThatBlocks(i, 200))
.sequential()
.elapsed()
.subscribe(i -> System.out.printf(" %s ", i));
输出类似于:
[210,3] [5,1] [0,2] [0,4] [196,6] [0,8] [0,5] [4,7] [196,10] [0,9]
如您所见,第一个响应是在210
ms 之后出现的,然后是 3 个响应,0
两者之间的时间接近。该循环一次又一次地重复。这是您应该使用平行助焊剂的地方。请注意,创建更多数量的线程并不能保证性能,因为当有更多数量的线程时,上下文切换会增加很多开销,因此应该在部署之前对代码进行测试。如果有很多阻塞调用,每个 cpu 有超过 1 个线程可能会提高性能,但如果调用是 cpu 密集型的,那么每个 cpu 有多个线程会由于上下文切换而降低性能。
所以总而言之,它总是取决于你想要实现什么。