Spring Reactive已经过时了吗? 螺纹连接反转

除了Spring的依赖注入仅解决控制反转问题的1/5之外,Spring Reactive还基于事件循环。 尽管还有其他流行的事件循环驱动解决方案(NodeJS,Nginx),但单线程事件循环是每个请求线程(线程池)朝另一个方向摆动。 在事件循环与每个请求线程竞争的情况下,是否没有某种模式可以使它们成为基础? 好吧,实际上是的!

但是在开始之前,让我们看一下有关事件循环和每个请求线程的问题。 如果您对该解决方案更感兴趣,则可以跳过接下来的两个部分。

螺纹连接问题

事件循环

首先,“线程耦合”? 为什么要担心? 对于事件循环来说,单线程本质要求所有I / O都必须异步进行。 如果需要阻止数据库或HTTP调用,它将阻止单个事件循环线程并支撑系统。 这种限制本身就是一个很大的耦合问题,因为要使Reactive将所有I / O耦合到异步状态。 这意味着不再需要像JPA这样的ORM来简化对数据库的访问(因为JPA需要阻止数据库调用)。 是的,以前在应用程序中删除了40-60%的样板代码的东西现在已经不可用了(请重新写一遍!)

除了决定使用响应式模式的限制性I / O之外,还限制了使用多个处理器的能力,因为只有一个线程。 好的,反应式引擎的实例已复制到每个CPU,但是它们不能共享状态。 在两个事件循环之间共享状态的多线程含义很困难。 响应式编程非常困难,更不用说向其中添加多线程了。 是的,事件循环之间的通信可以通过事件进行。 但是,使用此方法在事件循环之间使共享状态的重复副本保持同步会产生一些可以避免的问题。 基本上,您会被告知要设计您的反应性系统,以免发生这种情况。

因此,您被卡在一个线程上。 所以呢? 好吧,如果您执行计算量大的操作(例如安全密码学(JWT)),则会产生调度问题。 通过在单个线程上,必须先完成此操作,然后才能执行其他任何操作。 使用多个线程,操作系统可以在时间上切入其他线程,以处理其他占用较少CPU资源的请求。 但是,您只有一个线程,因此所有可爱的操作系统线程调度现在都丢失了。 在维修其他任何东西之前,您都不得不等待昂贵的CPU密集型操作完成。

哦,请忽略这些问题! 我们开发人员喜欢性能。 响应式的所有目的都是为了提高性能和改善可伸缩性。 较少的线程可以减少开销,从而提高吞吐量。 好的,是的,我将拥有性能更好的生产系统,从而可能降低硬件成本。 但是,由于来自单线程事件循环的耦合限制,构建和增强该生产系统的速度将大大降低。 更不用说,必须重写算法才能避免占用CPU。 与缺乏足够的云硬件供应相比,由于开发人员稀缺,因此争论规模成本可能仅适用于那些罕见的大型系统。

我们会做出很多反应。 这可能是因为我们还没有充分考虑过这一点。 因此,可能是为什么Reactive框架警告不要更改整个销售。 它们通常指示响应模式仅适用于较小且较不复杂的系统。

每个请求线程(线程池)

另一方面,每个请求线程模式(例如Servlet 2.x)使用​​线程池来处理扩展。 它们分配一个线程来服务请求,并通过具有多个(通常是池化的)线程进行扩展。

我们可能会读到许多文章称Reactive超出了每个请求线程的规模限制,但是每个请求线程的主要问题实际上不是性能,也不是规模。 每个请求线程的问题在您的应用程序中更为宽松,实际上会污染整个体系结构。

要查看此问题,只需看一下调用方法:

 Response result = object.method(identifier); 

该方法的实现应如下:

 @Inject Connection connection;  @Inject HttpClient client;  public Result method(Long identifier) { // Retrieve synchronous database result ResultSet resultSet = connection.createStatement() .executeQuery( "<some SQL> where id = " + identifier); resultSet.next(); String databaseValue = resultSet.getString( "value" ); // Retrieve synchronous HTTP result HttpResponse response = client.send( "<some URL>/" + databaseValue); // Return result requiring synchronous results to complete return new Result(response.getEntity());  } 

这给请求的线程带来了一个耦合问题,可能会污染整个体系结构。 是的,您刚刚在请求线程上放置了一个耦合到其他系统。

当数据库调用是同步的时,HTTP调用也迫使下游系统同步响应。 我们不能将HTTP调用更改为异步调用,因为请求线程希望继续执行从该方法返回的结果。 与请求线程的这种同步耦合不仅限制了调用,还限制了下游系统必须提供同步响应。 因此,每个请求线程的线程耦合可能会污染您的其他系统,甚至可能污染整个体系结构。 难怪同步HTTP调用的REST微服务模式如此流行! 这是一种迫使自己自上而下地在系统上的模式。 听起来像每个请求线程和Reactive在强制一切自上而下支持自己方面都持有相同的观点。

支持I / O的线程

总之,问题如下。

单线程事件循环:

  • 仅将您耦合到异步通信(不再提供简单的JPA代码)
  • 只是避免了多线程,因为从事件队列执行事件的两个线程会产生大量的同步问题(可能会降低解决方案的速度,并导致难以为最好的开发人员编写的并发错误)
  • 失去了线程调度的优势,即操作系统已花费大量精力进行优化

而按请求线程解决方案:

  • 仅将您耦合到同步通信(因为可以立即看到结果;不久后不会通过回调)
  • 由于管理更多的线程,因此具有较高的开销(单线程事件循环),因此可伸缩性较差

实际上,可以考虑从同步通信(每个请求线程)到异步通信(单线程事件循环)之间的线程池和响应式单线程之间的钟摆摆动。 剩下的问题实际上是专门为支持每种类型的通信而构建的线程模型的实现约束。 加上同步通信在下游系统上造成的耦合,这种摆动到异步通信的举动并不是一件坏事。

所以问题是,为什么我们被迫只选择一种沟通方式? 为什么我们不能同时使用同步和异步通信样式?

好吧,我们不能将异步调用放入同步方法调用中。 没有机会进行回调。 是的,我们可以阻止在回调中等待,但是Reactive会认为自己在规模上具有优势,因为其中涉及额外的线程开销。 因此,我们需要异步代码来允许同步调用。

但是,我们不能将同步调用放入事件循环中,因为它会中断事件循环线程。 因此,我们需要额外的线程来进行同步调用,以允许事件循环线程继续进行其他事件。

反应性就是答案。 使用调度程序:

 Mono blockingWrapper = Mono.fromCallable(() -> { return /* make a remote synchronous call */  }).subscribeOn(Schedulers.elastic()); 

来自http://projectreactor.io/docs/core/release/reference/#faq.wrap-blocking的代码

是的,现在我们可以在事件循环中进行同步调用了。 问题解决了(很好)。

好吧,如果您可以相信已将所有同步调用正确包装在Callables中,则会对其进行排序。 弄错了,那么您就阻塞了事件循环线程并暂停了应用程序。 至少在多线程应用程序中,只有特定请求受苦,而不是整个应用程序受苦。

无论如何,对我而言,这似乎比实际解决问题更多的工作。 哦,等等,一切都需要自下而上地进行反应,这样才能解决此问题。 只是不要阻塞呼叫,而是将所有驱动程序和整个技术堆栈更改为Reactive。 总体而言,“以一种仅与我们集成的方式改变一切以适合我们的方式”似乎非常接近技术供应商的锁定-无论如何,我认为。

因此,我们可以考虑一个允许同步调用并且不非常依赖开发人员正确实现的解决方案吗? 为什么是!

反转螺纹联轴器

异步通信驱动的Reactive单线程事件循环(不好意思)被认为是正确的解决方案。 开发人员使用调度程序解决了同步通信。 在这两种情况下,Reactive函数都使用为其指定的线程来运行:

  • 异步函数与事件循环的线程一起执行
  • 通过调度程序中的线程执行的同步功能

函数执行线程的控制在很大程度上取决于开发人员能否正确执行。 开发人员有足够的精力专注于构建代码以满足功能要求。 现在,开发人员密切参与了应用程序的线程处理(每请求线程总是从开发人员那里某种程度上抽象出来的)。 对线程的这种亲密关系大大增加了构建任何Reactive的学习曲线。 另外,当开发人员在凌晨2点将其拔出时,他们会松开很多头发,以使代码在该截止日期或生产修复中正常工作。

那么我们可以从必须正确执行线程的工作中删除开发人员吗? 更重要的是,我们在哪里控制选择线程?

让我们看一个简单的事件循环:

 public interface AsynchronousFunction { void run();  }  public void eventLoop() { for (;;) { AsynchronousFunction function = getNextFunction(); function.run(); }  } 

好吧,我们唯一可以控制的对象就是异步函数本身。 使用Executor指定线程,我们可以如下增强事件循环:

 public interface AsynchronousFunction { Executor getExecutor(); void run();  }  public void eventLoop() { for (;;) { AsynchronousFunction function = getNextFunction(); function.getExecutor().execute(() -> function.run()); }  } 

现在,这允许异步函数指定其所需的线程,如下所示:

  • 通过同步执行器使用事件循环线程:getExecutor(){return(runnable)-> runnable.run(); }
  • 通过线程池支持的Executor使用单独的线程进行同步调用:getExecutor(){return Executors.newCachedThreadPool(); }

控件被反转,以便开发人员不再负责指定线程。 该函数现在指定用于执行自身的线程。

但是,我们如何将执行程序与功能关联?

我们使用控制反转的ManagedFunction :

 public interface ManagedFunction { void run();  }  public class ManagedFunctionImpl implements ManagedFunction, AynchronousFunction { @Inject P1 p1; @Inject P2 p2; @Inject Executor executor; @Override public void run() { executor.execute(() -> implementation(p1, p2)); } private void implementation(P1 p1, P2 p2) { // Use injected objects for functionality }  } 

请注意,仅包含相关的ManagedFunction详细信息。 请参阅(耦合)控件的反转以获取ManagedFunction的更多详细信息。

通过使用ManagedFunction,我们可以将Executor与增强事件循环的每个函数相关联。 (实际上,由于Executor封装在ManagedFunction中,因此我们可以返回到原始事件循环)。

因此,现在不再需要开发人员使用调度程序,因为ManagedFunction负责使用哪个线程来执行函数的逻辑。

但这只是将开发人员从代码正确配置到配置的问题。 在为函数指定正确的线程(执行程序)时,如何减少开发人员的错误?

确定执行线程

ManagedFunction的一个属性是所有对象都被依赖注入。 除非注入了依赖项,否则没有对系统其他方面的引用(强烈建议不要使用静态引用)。 因此,ManagedFunction的依赖关系注入元数据提供了ManagedFunction使用的所有对象的详细信息。

了解函数使用的对象有助于确定函数的异步/同步性质。 要将JPA与数据库一起使用,需要一个Connection(或DataSource)对象。 要对微服务进行同步调用,需要HttpClient对象。 如果ManagedFunction不需要这些,则可以安全地考虑没有进行阻塞通信。 换句话说,如果ManagedFunction没有注入HttpClient,则它将无法进行HttpClient同步阻塞调用。 因此,可以安全地由事件循环线程执行ManagedFunction,而不会暂停整个应用程序。

因此,我们可以识别一组依赖关系,这些依赖关系指示ManagedFunction是否需要由单独的线程池执行。 我们知道系统中的所有依赖项,因此可以将它们分类为异步/同步。 或更恰当地说,是否可以在事件循环线程上安全使用依赖项。 如果依赖关系不安全,则需要该依赖关系的ManagedFunctions由单独的线程池执行。 但是什么线程池?

我们只使用一个线程池吗? 好吧,响应式调度程序可以灵活地为涉及阻塞调用的各种功能使用/重用不同的线程池。 因此,在使用多个线程池时,我们需要类似的灵活性。

我们通过将线程池映射到依赖项来使用多个线程池。 好的,这有点使您动脑了。 因此,让我们用一个例子来说明:

 public class ManagedFunctionOne implements ManagedFunction { // No dependencies // ... remaining omitted for brevity  }  public class ManagedFunctionTwo implements ManagedFunction { @Inject InMemoryCache cache; // ...  }  public class ManagedFunctionThree implements ManagedFunction { @Inject HttpClient client; // ...  }  public class ManagedFunctionFour implements ManagedFunction { @Inject EntityManager entityManager; // meta-data also indicates transitive dependency on Connection // ...  } 

现在,我们具有以下线程配置:

相依性 线程池
HttpClient 线程池一
连接 线程池二

然后,我们使用依赖关系将ManagedFunctions映射到线程池:

托管功能 相依性 执行者
ManagedFunctionOne,
ManagedFunctionTwo
(线程池表中没有) 事件循环线程
ManagedFunction3 HttpClient 线程池一
托管功能四 连接(作为EntityManager的传递依赖项) 线程池二

线程池(执行器)用于ManagedFunction的决定现在只是映射配置。 如果某个依赖项调用了阻塞调用,它将被添加到线程池映射中。 使用此依赖项的ManagedFunction将不再在事件线程循环上执行,从而避免了应用程序暂停。

此外,大大减少了丢失阻塞呼叫的可能性。 由于对依赖项进行分类相对容易,因此遗漏阻塞调用的机会较小。 另外,如果缺少依赖项,则仅是对线程池映射的配置更改。 它是固定的,无需更改代码。 随着应用程序的成长和发展,它特别有用。 这与要求代码更改和开发人员需要认真思考的反应式调度程序不同。

由于现在由框架(而不是应用程序代码)控制执行ManagedFunction的执行线程,因此它有效地反转了对执行线程的控制。 开发人员代码不再线程化。 框架根据ManagedFunctions的依赖关系特性对其进行配置。

办公楼层

从理论上讲,这一切都很好,但是请向我展示工作代码!

OfficeFloor( http://officefloor.net )是本文讨论的线程控制模式反转的实现。 我们发现框架的线程模型过于僵化,导致变通,例如Reactive Scheduler。 我们正在寻找基础模式来创建不需要这种解决方法的框架。 可以在教程中找到代码示例,我们重视所有反馈。

请注意,尽管OfficeFloor遵循线程控制的反转,但考虑其他方面(例如,依赖关系上下文,变异状态,线程局部变量,线程亲和力,背压和减少的锁定以提高性能),其实际的线程模型更为复杂。 但是,这些是其他文章的主题。 但是,正如本文所强调的,OfficeFloor应用程序的线程是基于依赖关系映射的简单配置文件。

结论

线程的控制权反转允许函数指定它自己的线程。 由于线程是由注入的Executor控制的,因此该模式称为Thread Injection 。 通过允许注入,线程的选择由配置而不是代码确定。 这使开发人员免于将线程编码到应用程序中的潜在容易出错的错误任务。

线程注入的另一个好处是可以根据应用程序运行的计算机来定制线程映射配置。 在具有许多CPU的计算机上,可以配置更多线程池以利用操作系统的线程调度。 在较小的计算机(例如嵌入式计算机)上,可以更多地重用线程池(对于单用途应用程序,甚至有可能不使用这些线程池,因为它们可以容忍阻塞以减少线程计数)。 这将不会对应用程序进行任何代码更改,而只需进行配置更改。

此外,可能占用事件循环的计算量大的功能也可以移至单独的线程池。 只需在线程池映射中添加此计算的依赖项,所有进行该计算的ManagedFunctions现在就不会占用事件循环线程。 线程注入的灵活性不仅仅是支持同步/异步通信。

由于线程注入全部由配置驱动,因此不需要更改代码。 实际上,开发人员根本不需要任何线程编码。 这是反应式调度程序无法提供的。

因此,问题是,您是否想将自己绑定到单线程事件循环,而这实际上只是异步I / O的单一目的实现? 还是您想使用更灵活的东西?

翻译自: https://www.javacodegeeks.com/2019/04/spring-reactive-already-obsolete-inversion-thread-coupling.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/342514.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从交换机浅谈安防视频会卡顿现象

解决视频卡顿、重新定义适用于安防视频数据的交换机才是正途。超高性价比&#xff08;与企业级相当&#xff09;、宽温&#xff08;保证室外应用稳定&#xff09;、大缓存&#xff08;保证每一个端口大数据帧不卡顿&#xff09;、高防护&#xff08;IP30 40&#xff0c;保证适用…

低功耗远距离lora模块:SX1262与SX1278、SX1276射频无线收发器芯片对比分析以及选型指南

1、产品简述 SX1278/6是Semtech公司在2013年推出的一款远距离、低功耗的无线收发器&#xff0c;是一款性能高的物联网无线收发器&#xff0c;具备特殊的LoRa调制方式&#xff0c;在一定程度上增加了通信距离&#xff1b;而SX1262是一款新产品&#xff0c;同样由Semtech公司在2…

[渝粤教育] 北京师范大学 中国哲学 参考 资料

教育 -中国哲学-章节资料考试资料-北京师范大学【】 第一周 《中国哲学》课程绪论&#xff1b; “先秦诸子”之先秦儒家&#xff08;单元测验&#xff09; 1、【单选题】荀子所指的“心”是&#xff08; &#xff09; A、形上之心 B、逻辑之心 C、天心 D、道德之心 参考资料【 …

[渝粤教育] 北京理工大学 工程热力学 参考 资料

教育 -工程热力学-章节资料考试资料-北京理工大学【】 第一章单元作业 第一章单元测验 1、【单选题】单元系统平衡态时各点的状态参数&#xff08; &#xff09;。 A、必定是均匀一致的 B、必定是接近相等的 C、是在不断变化的 D、不一定是均匀一致的 参考资料【 】 2、【单选题…

CC1101超低功耗无线模块在物联网能门锁中的应用

CC1101是TI的超低功耗无线收发芯片&#xff0c;支持sub-1 GHz频段&#xff0c;其主要针对工业、科研和医疗(ISM)以及短距离无线通信设备(SRD)。CC1101可提供对数据包处理、数据缓冲、突发传输、接收信号强度指示(RSSI)、空闲信道评估(CCA)、链路质量指示&#xff08;LQI&#x…

交换机发生网络通信故障问题时该怎么办?

交换机故障一般可以分为硬件故障和软件故障两大类&#xff0c;交换机虽然不常发生故障&#xff0c;但是一旦发生故障后都不太好检测与排除。那么&#xff0c;当交换机发生网络通信故障了该怎么办呢&#xff1f;今天就由飞畅科技的小编来给大家说说交换机网络通信故障怎么办&…

[渝粤教育] 北京科技大学 离散数学 参考 资料

教育 -离散数学-章节资料考试资料-北京科技大学【】 命题逻辑单元测验 1、【单选题】以下哪个语句是命题&#xff1f; A、请不要抄袭&#xff01; B、太阳是行星。 C、这里能抽烟吗&#xff1f; D、x – y 3 参考资料【 】 2、【单选题】以下哪个命题是原子命题&#xff1f; A…

E103-W01产品WiFi无线模块快连技术在智能家居中的应用

WiFi是一种允许电子设备连接到一个无线局域网&#xff08;WLAN&#xff09;的技术&#xff0c;通常使用2.4G UHF或5G SHF ISM 射频频段。连接到无线局域网通常是有密码保护的&#xff1b;但也可是开放的&#xff0c;这样就允许任何在WLAN范围内的设备可以连接上。Wi-Fi是一个无…

亿佰特Wifi模块、蓝牙模块和Zigbee模块协议在物联网智能家居上的应用指南

物联网是新一代信息技术的重要组成部分&#xff0c;也是“信息化”时代的重要发展阶段。其英文名称是&#xff1a;“Internet of things&#xff08;IoT&#xff09;”。顾名思义&#xff0c;物联网就是物物相连的互联网。这有两层意思&#xff1a;其一&#xff0c;物联网的核心…

[渝粤教育] 北部湾大学 团体心理辅导 参考 资料

教育 -团体心理辅导-章节资料考试资料-北部湾大学【】 单元作业 团体心理辅导概述 单元测验 团体心理辅导概述 1、【单选题】团体心理辅导是在( )情境中提供心理帮助与指导的种心理辅导与治疗的形式。 A、个体 B、社会 C、社区 D、团体 参考资料【 】 2、【单选题】团体心理辅导…

交换机选用要点及订货主要技术条件

现如今&#xff0c;随着通信业的发展以及国民经济信息化的推进&#xff0c;以太网技术已成为当今最重要的一种局域网组网技术&#xff0c;网络交换机也就成为了最普及的交换机&#xff0c;网络交换机市场呈稳步上升态势。那么&#xff0c;我们在选购网络交换机的时候该注意哪些…

javase11源码文件_JavaSE 7,8:确定特定文件系统支持的视图

javase11源码文件如果您对文件或目录有疑问&#xff0c;例如文件是否隐藏&#xff0c;目录是否存在&#xff0c;文件大小以及拥有者&#xff0c;则可以从元数据中获得这些问题&#xff08;以及许多其他问题&#xff09;的答案&#xff0c;这是关于其他数据的数据。 NIO.2将元数…

物联网无线通信应用的电源模块设计指南

无线通信早已深入人们的生活&#xff0c;设计无线模块的公司也越来越多&#xff0c;从事射频设计的朋友也越来越多&#xff0c;以下是我总结的一些无线模块的电源设计注意事项分享给各位希望对各位读者有所帮助。如果有不同意见或建议的也希望大家通过成都亿佰公司的官网微博及…

[渝粤教育] 华中农业大学 2021年秋动物繁殖学SPOC(杨利国) 参考 资料

教育 -2021年秋动物繁殖学SPOC&#xff08;杨利国&#xff09;-章节资料考试资料-华中农业大学【】 第一章单元测试

亿佰特电源模块:无线通信模块电平转换指南

在我们电路设计中&#xff0c;常常会遇到无线通信电平转换的问题&#xff0c;在应用电平转换的措施之前还需要判断进行电平转换的必要性。 如果你是用的是3.3V器件作为输出&#xff0c;而5V器件作为接收&#xff0c;那么这种低电平输出不会损坏器件&#xff0c;而且大部分3.3V…

[渝粤教育] 华中农业大学 经济学原理 参考 资料

教育 -经济学原理-章节资料考试资料-华中农业大学【】 第一章测试题 1、【单选题】现有资源不能充分满足人的欲望这一事实被称为&#xff08; &#xff09;。 A、机会成本 B、稀缺性 C、规范经济学 D、生产什么的问题 参考资料【 】 2、【单选题】经济学可定义为&#xff08; &…

路由器和交换机哪个更好?路由器交换机怎么连接?

路由器与交换机是我们常见的两种设备&#xff0c;它们的功能非常相似&#xff0c;当我们需要连接网络的设备比较多的时候&#xff0c;路由器往往就显得有点“力不从心”了&#xff0c;此时就需要用到交换机&#xff0c;它可以让更多的设备连接上网络&#xff0c;在作用上似乎与…

当HTTP状态代码不足时:处理Web API错误报告

RESTful Web API设计的一个领域&#xff08;经常被忽视&#xff09;是如何报告与业务或应用程序有关的错误和问题。 首先要想到HTTP状态代码的正确用法&#xff0c;尽管它非常方便&#xff0c;但通常它的信息量还不够。 让我们以400错误请求为例。 是的&#xff0c;它清楚地表明…

[渝粤教育] 华中科技大学 模拟电子技术基础 参考 资料

教育 -模拟电子技术基础-章节资料考试资料-华中科技大学【】 绪论测验题 1、【单选题】当输入信号频率为fL或fH时&#xff0c;放大电路电压增益的幅值约下降为通带内水平增益的 。 A、0.5倍 B、0.7倍 C、0.9倍 D、1倍 参考资料【 】 2、【单选题】某放大电路在负载开路时的输出…

远程抄表系统(AMR/AMI)中无线模块选型指南

1.概述 远程抄表系统是为提高水表、电表等能耗参数的综合计费管理水平而设计的新兴技术。它以全自动的抄表方式取代了传统的人工抄表方式&#xff0c;和同类抄表系统相比&#xff0c;具有网络结构自适应、免调试、免维护、运行稳定、方便扩展的特点。该系统采用先进的无线网络…