当前位置: 首页 > 知识库问答 >
问题:

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

沙靖琪
2023-03-14

我试图理解什么时候我们会使用像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个答案

云景焕
2023-03-14

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

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

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赛车有什么意义吗?

我把这个决定留给你。

 类似资料:
  • 下面的代码是否阻止调用,如果是,如何使其成为非阻塞?i、 e.使反应式Java流的使用变得无用?如何在不阻塞呼叫的情况下分页? 目前,我有一个webClient调用后端服务,该服务返回

  • 但在被动非阻塞I/O中,我们会在异步中调用socket,因此如果请求太多,当前会调用大量的web socket。OS套接字堆栈能hold住吗?缓冲区怎么样? 例如,同时建立数百万套接字连接。 套接字占用10s。 CPU计算1ms. 是否会在第一个插座返回之前进行10,000个插座连接? 非常感谢你回答我的问题!

  • 我正在尝试创建一个消费者-生产者程序,其中消费者线程生产者的数字填充数组,消费者线程打印填充数组的数字。目前,我可以填充数组并在使用者/生产者线程之间来回传递数据,但我希望生产者创建数字的速度比使用者处理数字的速度快。 此刻,每1秒产生一个数字,每3消耗一个数字。在消耗一个之前应该产生两个数字,但是我的生产者线程正在等待,直到它产生的数字被消耗。 我试过移动互斥锁和解锁,还有信号,但我没有得到它的

  • 关于这一点的文献不多。我想知道,我们应该先打电话吗 或 在https://developers.google.com/admob/android/quick-start 尽管谷歌建议致电<code>MobileAds。尽早初始化 在加载广告之前,让您的应用通过调用MobileAds.initialize()初始化Mobile Ads SDK,MobileAds.initialize初始化SDK,并

  • 我们使用JDBC源连接器将数据从表同步到Kafka中的主题(称为主题1)。正如我们所知,这只捕获插入和更新,我们添加了一个触发器来捕获删除。此触发器捕获删除的记录并写入一个新表,该表与另一个Kafka主题(称为主题2)同步。 我们已经将JDBC源连接器配置为使用AvroConverter。 现在,我们已经编写了一个Kafka streams逻辑,它使用本主题2中的数据并发布到主题1。我的问题是Ka

  • 问题内容: 我正在使用’multiprocess.Pool.imap_unordered’如下 我需要打电话或之后的for循环? 问题答案: 不,您没有,但是如果您不再使用游泳池,那可能是个好主意。 Tim Peters在此SO帖子中致电或致电的理由很明确: 至于Pool.close(),您应该在永远不会将更多工作提交给Pool实例的情况下(且仅在)进行调用。因此,通常在主程序的可并行化部分完成时