反应式编程已经存在了一段时间,但在最近几年中引起了部分人极大的兴趣。大多原因都是:传统的命令式编程在满足当今的需求时存在一些局限性,在当今的应用程序中,应用程序需要具有高可用性,并且在高负载期间也需要低响应时间。
每个请求模型的线程
为了了解什么是反应式编程及其带来的好处,让我们首先考虑使用Spring开发Web应用程序的传统方法-使用Spring MVC并将其部署在Servlet容器(例如Tomcat)上。

Servlet容器有一个专用的线程池来处理HTTP请求,每个传入请求将分配一个线程,并且该线程将处理HTTP请求的整个生命周期(“每个请求模型的线程”)。这意味着应用程序将只能处理与线程池大小相等的并发请求数。可以配置线程池的大小,但是由于每个线程都保留一些内存(通常为1MB),因此我们配置的线程池大小越大,内存消耗就越大。
如果应用程序是根据基于微服务的体系结构设计的,则我们有更好的根据负载进行扩展的可能性,但是高内存利用率仍然要付出代价。因此,对于具有大量并发请求的应用程序,每个请求模型的线程可能会变得非常奢侈。
基于微服务的体系结构的一个重要特征是应用程序是作为大量独立进程运行的,通常跨多个服务器运行。将传统的命令式编程与服务请求之间的同步请求/响应调用一起使用,意味着线程经常被阻塞,以等待其他服务的响应。这导致资源的巨大浪费。
等待I / O操作在等待其他类型的I / O操作(例如数据库调用或文件读取)完成时,也会发生相同类型的浪费。在所有这些情况下,发出I / O请求的线程将被阻塞并等待空闲,直到I / O操作完成为止,这称为阻塞I / O。在这种情况下,正在执行的线程被阻塞,仅在等待响应时,这意味着浪费线程,因此也浪费了内存。
图1-线程阻塞等待响应
响应时间传统命令式编程的另一个问题是服务需要执行多个I / O请求时的响应时间。例如,服务A可能需要调用服务B和C,并进行数据库查找,然后返回一些聚合数据。这意味着服务A的响应时间除自身的处理时间外,还应为:
服务B的响应时间(网络延迟+处理)。服务C的响应时间(网络延迟+处理)。数据库请求的响应时间(网络延迟+处理)。图2-按顺序执行的调用
如果没有实际逻辑上的理由依次执行这些调用,那么如果并行执行这些调用,肯定会对服务A的响应时间产生非常积极的影响。即使支持使用CompletableFutures在Java中进行异步调用并注册回调,也可以在应用程序中广泛使用这种方法,这会使代码更加复杂,并且难以阅读和维护。
在微服务领域中可能发生的另一种类型的问题是,当服务A向服务B请求某些信息时,例如上个月的所有订单。如果订单量很大,服务A一次检索所有这些信息可能会成为问题。服务A可能会被大量数据淹没,并可能导致内存不足错误等等原因。
总结上面描述的不同问题是反应式编程要解决的问题。
简而言之,响应式编程的优点是:
每个请求模型都远离线程,并且可以以较少的线程数处理更多请求防止线程在等待I / O操作完成时阻塞轻松进行并行调用使客户端可以通知服务器它可以处理多少负载什么是反应式编程?定义
Spring文档中使用的反应式编程的简短定义如下:
“简单地说,反应式编程是关于非阻塞应用程序,它们是异步的和事件驱动的,并且需要少量的线程来扩展。该定义的关键方面是背压的概念,它是一种确保生产者不会产生压力的机制。压倒了消费者。”
说明
那么如何实现所有这些呢?
简而言之:通过使用异步数据流进行编程。假设服务A要从服务B中检索一些数据。使用反应式编程方式,服务A将向服务B发出请求,服务B立即返回(非阻塞且异步)。
然后,请求的数据将作为数据流提供给服务A,其中服务B将为每个数据项一个接一个地发布onNext事件。发布所有数据后,将通过onComplete事件发出信号。如果发生错误,将发布onError事件,并且不再发射任何项目。
图3-反应性事件流
响应式编程使用一种功能样式方法(类似于Streams API),这使对流执行不同类型的转换成为可能。流可以用作另一流的输入。流可以合并,映射和过滤。
反应系统
反应式编程是开发“反应式系统”时的一种重要的实现技术,这是“反应式宣言”中描述的概念,强调了将现代应用程序设计为:
响应式(及时响应)弹性(在故障情况下也保持响应)弹性(在不同的工作量下保持响应)消息驱动(依赖异步消息传递)建立响应式系统意味着要处理诸如关注点分离,数据一致性,故障管理,消息传递实现的选择等问题。
响应式编程可以用作一种实现技术,以确保各个服务使用异步的非阻塞模型,但是要将系统整体设计为响应式系统,则还需要进行设计,同时还要考虑所有其他方面。
背景反应性X
2011年,Microsoft发布了.NET的Reactive Extensions(ReactiveX或Rx)库,以提供一种简便的方法来创建异步的,事件驱动的程序。在几年的时间里,Reactive Extensions被移植到多种语言和平台,包括Java,JavaScript,C ++,Python和Swift。ReactiveX迅速成为一种跨语言标准。Java实现(RxJava)的开发由Netflix驱动,2014年发布了1.0版。
ReactiveX使用了来自“四人帮”的迭代器模式和观察者模式的混合。区别在于,与Iterators基于正常拉动的行为相比,使用了推模型。除了观察变化之外,还将完成和错误等情况按要求通知给相应的人。
反应流规范
随着时间的流逝,通过Reactive Streams的努力开发了Java的标准化。反应性流是旨在由为JVM构建的反应性库实现的较小规范。它指定要实现的类型以实现不同实现之间的互操作性。该规范定义了异步组件与背压之间的相互作用。Flow API在Java 9中采用了反应式流。Flow API的目的是充当一种互操作规范,而不是像RxJava这样的最终用户API。
该规范涵盖以下接口:
发行人
这表示数据生产者/数据源,并具有一种使订阅者注册到发布者的方法。
public interface Publisher<T> { public void subscribe(Subscriber<? super T> s);}
消费者:
具有以下方法:
public interface Subscriber<T> { public void onSubscribe(Subscription s); public void onNext(T t); public void onError(Throwable t); public void onComplete();}
onSubscribe Publisher 在处理开始之前由调用, 用于将Subscription 对象从 传递 Publisher 到。 Subscriber onNext 用于发出新物品的信号 onError 用于表示 Publisher 已发生故障并且将不再发射任何物品 onComplete 用于表示所有物品均已成功发射
订阅:
订阅包含使客户端能够控制发布者的项目散发(即提供背压支持)的方法。
public interface Subscription { public void request(long n); public void cancel();}
request 允许 Subscriber 通知 Publisher 要发布多少个其他元素 cancel 允许订户取消商品的进一步发射 Publisher
处理器:
如果实体将转换传入的项目,然后将其进一步传递给另一个订户,则需要Processor接口的实现。这既充当订户又充当发布者。
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {}
项目反应堆
从版本5开始,Spring Framework就支持反应式编程。该支持建立在Project Reactor之上。
Project Reactor(或仅仅是Reactor)是用于在JVM上构建非阻塞应用程序的Reactive库,它基于Reactive Streams规范。