提问者:小点点

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


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

但我也在这里读过https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html

评估应用程序的一种简单方法是检查其依赖关系。如果要使用阻塞持久性API(JPA、JDBC)或网络API,Spring MVC至少是常见架构的最佳选择。对于Reactor和RxJava来说,在单独的线程上执行阻塞调用在技术上是可行的,但您不会充分利用非阻塞web堆栈。

这似乎表明了完全相反的情况。我了解到,如果您可以流式传输信息,那么反应式更有用。例如,如果您有一个前端要求提供一个列表和一个反应源,它可以在信息可用时为您提供信息,而不是在信息完成时仅提供全部信息。

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

我知道还有其他考虑因素,比如代码的复杂性。我知道反应式方法很难实现,但我只是想问一下性能和可伸缩性。


共1个答案

匿名用户

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

但是,如果您了解反应式解决方案试图解决的问题,以及它是如何解决的,那么您就可以更好地了解如何做出更好的决策。

java中大多数web服务器使用的传统servlet方法为每个请求分配一个线程。因此,当一个请求传入时,将为其分配一个线程,然后该线程处理该请求。如果您的服务器随后阻止了对其他服务器的调用,则分配的线程需要等待响应返回。

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

这是一个很大的资源花费只是等待。

如此古老的方式:

  • 每个请求一个线程

Reactive通过使用称为事件循环的东西来解决这个问题,然后使用一个小型线程池来调度事件循环上的工作。

我练习你可以这样看,一个事件循环,然后可能是10个线程,所有线程都安排工作,事件循环一直工作,调度程序只是安排一长串工作让事件循环推动。所以所有的线程总是100%忙碌。

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

但这意味着我们需要100%无阻塞。Imagen在这种情况下,我们有一个阻塞调用,然后整个eventloop将停止,所有计划的作业将停止,整个机器将冻结,直到我们“取消阻塞”。

如此反应性:

  • 事件循环完成所有工作

所以我们基本上是用内存换取CPU能力。

那么什么是阻塞呼叫呢?大多数呼叫都是阻塞的,就像你调用另一个服务,你需要等待一样。但这就是反应式发光的地方,因为它还有另一个特性。

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

我们是所谓的线程不可知论者。

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

那么什么是糟糕的阻塞呼叫呢?当你调用线程相关的东西时。这意味着您正在调用依赖于进行调用以处理响应的同一线程的某个对象,然后我们需要阻塞该线程并等待响应。

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

因此,例如,使用ThreadLocal的所有内容在反应式世界中都很糟糕,这就是主要问题之一。JDBC(数据库驱动程序规范)是天生阻塞编写的。因为它依赖于本地线程来跟踪事务才能回滚。这意味着所有使用JDBC的数据库调用在非/阻塞反应式应用程序中都是不可使用的,这意味着您必须使用使用R2DBC规范的数据库驱动程序。

但是rest调用不会阻塞,因为它不依赖于线程,除非您使用依赖于线程的功能,例如Spring webClient不使用的ThreadLocal。

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

因此,如果您的服务只调用大量硬阻塞服务(如旧数据库),您将不得不不断选择退出反应式框架,因此您基本上只是使用反应式框架构建旧的传统servlet Web服务,这是一种反模式。所以我不推荐这样做。

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

Reactor参考资料中说明了这一点以及更多,我建议你做更多的研究。

如果你的道路有很多弯道,没有直道,如果你总是不得不减速,并且经常转弯,那么买一辆F1赛车有什么意义吗?

我把这个决定留给你。