当我们有阻塞调用时,我们是否应该使用像 spring webflux 这样的反应式堆栈 Web 框架?

Should we use a reactive-stack web framework like spring webflux when we have blocking calls?

我想知道我们什么时候会使用像 webflux 这样的反应式堆栈框架。 我读过的文章似乎表明,当我们有很多阻塞调用时,我们会从反应式方法中受益。例如,如果我们有一个 webhook 服务需要调用客户端服务器来更新它们的信息。

不过我也看了这里https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html

A simple way to evaluate an application is to check its dependencies. If you have blocking persistence APIs (JPA, JDBC) or networking APIs to use, Spring MVC is the best choice for common architectures at least. It is technically feasible with both Reactor and RxJava to perform blocking calls on a separate thread but you would not be making the most of a non-blocking web stack.

而这似乎恰恰相反。 我读到如果您可以流式传输信息,反应式会更有用。例如,如果您有一个前端请求一个列表和一个反应源,它可以在信息可用时为您提供信息,而不是在完成后仅提供所有信息。

所以问题是我们什么时候应该使用响应式后端?当我们有很多阻塞调用时我们应该使用它吗?例如。对我们需要等待响应的客户端的 HTTP 调用。还是这完全是错误的用例?

我知道还有其他考虑因素,例如代码的复杂性。我知道反应式方法更难实施,但我在这里只询问性能和可伸缩性。

很难在这里给你任何具体的答案,因为我们不知道你的确切架构。

但是,如果您了解 Reactive 试图解决的问题,以及它是如何解决的,它可能会让您更好地洞察做出更好的决定。

java 中的大多数 Web 服务器使用的传统 servlet 方法为每个请求分配一个线程。所以当一个请求进来时,一个线程被分配给它,然后这个线程处理这个请求。如果您的服务器随后阻塞了对其他服务器的调用,则分配的线程需要等待响应返回。

这往往会导致 Web 服务器有数百个线程花费大量时间等待。当我说等待时,我的意思是等待很多。一个线程 90% 的时间都可能花在等待阻塞调用上。例如,Web 服务器中的处理可能需要 3 毫秒,然后它会阻塞数据库调用,线程需要等待 200 毫秒(不要在数字上引用我的话)。

光是等待就花费了大量资源。

老办法:

  • 每个请求一个线程
  • 如果有 300 个请求,那么我们有 300 个线程
  • 内存使用率高(每个线程都需要内存)
  • CPU 花了很多时间等待

Reactive 通过称为事件循环的东西解决了这个问题,然后是一个在事件循环上安排工作的小线程池。

我练习你可以这样设置,一个事件循环,然后可能有 10 个线程都在安排工作,事件循环一直在工作,调度程序只是为要通过的事件循环。所以所有的线程总是 100% 忙。

在 webflux 应用程序中,事件循环的数量通常取决于硬件中的内核数量。

但这意味着我们需要 100% non-blocking。想象一下,在这种情况下我们有一个阻塞调用,然后整个事件循环将停止,所有计划的作业将停止,并且整个机器将冻结直到我们“un-blocked”。

如此反应:

  • event-loop 完成所有工作
  • 调度工作的小线程池
  • 阻塞非常糟糕,可以冻结整个应用程序
  • 因为线程更少,内存占用更小
  • 更高CPU使用率
  • 可能更高的吞吐量

所以我们基本上是用记忆换取CPU能力。

那么什么是阻塞调用?好吧,大多数呼叫都是阻塞的,因为您呼叫另一项服务并且需要等待。但这就是反应式的闪光点,因为它还有一个特点。

由于每个请求没有一个特定的线程,任何线程都可以发出请求,但事情是这样的,任何线程都可以处理响应,它不必是同一个线程。

我们就是所谓的thread-agnostic.

我们的 non-blocking 服务可以对其他服务进行大量阻塞调用,并且仍然保持完整 non-blocking。因为当说 non-blocking 我的意思是我们在我们自己的应用程序内部 non-blocking。

那么什么是坏阻塞调用呢?好吧,当你调用线程相关的东西时。这意味着您正在调用的东西依赖于发出调用以处理响应的同一线程,那么我们需要阻塞该线程并等待响应。

如果我们需要进行调用,然后阻塞等待响应,然后处理响应,因为我们需要同一个线程,那么我们不能使用反应式,因为那样我们可能会阻塞事件循环,这将停止整个应用程序。

因此,例如,所有使用 ThreadLocal 的东西在反应性世界中都是不好的,这就是主要问题之一。JDBC(数据库驱动程序规范)编写时固有地阻塞.因为它依赖于本地线程来跟踪能够回滚的事务。这意味着所有使用 JDBC 的数据库调用在 non/blocking 反应式应用程序中不可用,这意味着您必须使用使用 R2DBC 规范的数据库驱动程序。

但是 rest 调用不会阻塞,因为它不依赖于线程,除非你使用线程相关的功能,比如 ThreadLocal,而 spring webclient 不会。

那么你的引述在说什么?好吧 spring 反应堆有一种机制,因此您可以将“旧方式”(每个请求一个线程)与新方式(线程不可知)混合使用。这意味着如果你有一个硬阻塞调用(使用 jdbc 驱动程序调用旧数据库),你可以明确地告诉框架“对这个数据库的所有调用都应该放在它自己的线程池中”所以你有点告诉框架为该特定调用使用旧方法(通过为该请求分配一个独占线程)。但请记住,这样做会失去反应式的所有好处。

所以如果你的服务e 仅调用大量硬阻塞服务(如旧数据库),您将不得不不断选择退出反应式框架,因此您基本上只需使用反应式框架构建一个旧的传统 servlet Web 服务,这有点像anti-pattern。所以我不建议这样做。

我在这里写的只是一般计算机知识,线程如何工作,rest 调用如何工作,数据库驱动程序如何工作。我无法解释计算机如何在单个堆栈溢出中工作 post。

Reactor reference 中说明了这一点以及更多内容,我建议您进行更多研究。

如果你的道路有很多转弯而且没有直道,如果你一直需要减速并一直转很多弯,那么购买 F1 赛车还有意义吗?

我把这个决定留给你。