13

想了解更多关于springboot webflux的底层并发模型?

对于 CPU 密集型 Web 服务,传统的阻塞多线程模型是否更合适?或者,根据本文https://people.eecs.berkeley.edu/~brewer/papers/threads-hotos-2003.pdf ,传统的线程池模型是否更适合?

如果我在反应器链中有一个阻塞步骤,我会使用 publishOn 将其调度到不同的线程池。这是否释放了原始线程并使整个链仍然非阻塞?

4

1 回答 1

23

WebFlux 适合的地方以及它在引擎盖下的工作方式

从我的角度来看,最适合 Spring WebFlux 的是网络密集型应用程序。在底层,Spring WebFlux 使用 Reactor-Netty,它是一个支持背压的Netty 包装器。Reactor-Netty 使用几乎相同的 Netty 线程策略并Runtime.getRuntime().availableProcessors() * 2为其内部创建线程EventLoopThreadPool。这意味着每个Thread是〜绑定到它自己的核心/处理器并且以最小的CPU资源争用工作。这种模型非常适用于端到端的非阻塞通信。因此,在传入请求以远程网络调用结束的情况下,该调用应该以非阻塞方式执行,以便同一个线程可以返回到偶数循环队列中的其余任务。这种技术使我们能够有效地利用我们的硬件,几乎没有花费在上下文切换和高争用上的开销,这是阻塞与大量相关线程通信的常见属性。

项目反应堆的作用

Spring WebFlux 中 Project Reactor 的核心作用是提供一个编程模型,以保持复杂的非阻塞、异步执行的清晰度。它隐藏了继续数据处理的复杂性,并允许我们轻松构建一个功能性的、声明性的元素处理管道。此外,Reactor 的线程模型只是几个运算符,可以在专用线程池上重新安排复杂和高效的元素处理,而不会让人头疼。在底层,使用了从 Java Core 库中获取的相同的 ThreadPools 和 ExecutorServices。

如何处理 CPU 密集型任务,Project Reactor 是否适合那里?

我会说 - Reactor Netty 也非常适合 CPU 密集型任务。但在这种情况下,应该适当地使用 Project Reactor。在复杂的算法处理或类似工作的情况下,最好使用纯 Java,因为 Reactor 会增加大约 100-150% 的性能开销。

如何使用 Project Reactor 和 Reactor-Netty (WebFlux) 构建高效的 CPU 密集型任务处理

我建议遵循“工作队列”模式,这样一旦前一个线程完成,每个线程都会执行一个新任务。

如果我们有一个 CPU 密集型任务,始终建议将其安排在专用线程池上。即使它会增加一点开销,我们也会有更高的 I/O 读写延迟,这是任何网络应用程序不可或缺的一部分。在 Netty 的情况下,我们将确定 Netty 的 EventLoop 只对网络进行读写,仅此而已。

要在专用线程池上安排任务,我们可以遵循以下代码示例中显示的技术:

@PostMapping
public Mono<CpuIntensiveResult> cpuIntensiveProcessingHandler(
    Mono<CpuIntensiveInput> monoInput
) {
    return monoInput
        .publishOn(Schedulers.fromExecutorService(myOwnDedicatedExecutor))
        .map(i -> doCpuIntensiveInImperativeStyle(i));
}

正如我们从上面的代码中看到的,使用 Project Reactor 库中的一个运算符,我们可以轻松地在专用线程池上安排工作处理。反过来,我们可以快速将任何现有的 Executor Service 包装到 Scheduler 中,并使用 whit-in Reactor 生态系统

阻塞多线程技术怎么样?

在通常情况下,我们的线程多于内核的多线程技术,我们不会获得任何好处。这里的缺点是相同的——上下文切换和争用。似乎这些任务是同时处理的。但是,系统调度程序在并发线程之间进行 CPU 时间分配的繁重工作。它将在几个密集任务之间共享相同的 CPU,因此最终导致每个任务的延迟更高。平均而言,处理时间将高于系统中相同数量的线程完成的相同工作。

关于线程模型作为“真正的”处理模型的一些注意事项。

根据上述白皮书,线程模型是真正的编程模型。我想,这篇论文的作者正在谈论Green Threads。Green Threads 可能是更好的编程模型,但最终,您必须遵循上面提到的 Reactor 编程模型的相同规则。Thread 和随后的命令式编程模型的缺点是无法处理数据流,而 Reactor 编程模型非常适合。

另外,我建议重新审视通用可伸缩性定律并审查争用和连贯性问题(这与当前的 Java 线程执行有关)。此外,下一篇论文还解释了可扩展性的一个很好的概述。

另一个高效使用异步 + 非阻塞请求处理的示例是 Facebook 架构,它在挑选负载时将工作队列转换为允许保持最低延迟的工作堆栈。

于 2018-12-08T16:13:56.843 回答