聊聊Spring Reactor反应式编程

前言

为了应对高并发环境下的服务端编程,微软提出了一个实现异步编程的方案 -,中文名称反应式编程。随后,其它技术也迅速地跟上了脚步,像 通过 引入了类似的异步编程方式。 社区也没有落后很多, 和 公司提供了 和 技术,让 平台也有了能够实现反应式编程的框架。

正文

函数式编程

函数式编程是种编程方式,它将计算机的运算视为函数的计算。函数编程语言最重要的基础是λ演算,而λ演算的函数可以接受函数当作输入(参数)输出(返回值)。 表达式对与大多数程序员已经很熟悉了, 以及 都是引入的 。

函数式编程的特点

惰性计算

函数是“第一等公民”

只使用表达式而不使用语句

没有副作用

反应式编程

反应式编程 是一种基于数据流变化传递声明式的编程范式。

反应式编程的特点

1. 事件驱动

在一个事件驱动的应用程序中,组件之间的交互是通过松耦合的生产者消费者来实现的。这些事件是以异步非阻塞的方式发送和接收的。

事件驱动的系统依靠推模式而不是拉模式投票表决,即生产者是在有消息时才推送数据给消费者,而不是通过一种浪费资源方式:让消费者不断地轮询等待数据

2. 实时响应

程序发起执行以后,应该快速返回存储结果的上下文,把具体执行交给后台线程。待处理完成以后,异步地将真实返回值封装在此上下文中,而不是阻塞程序的执行。实时响应是通过异步编程实现的,例如:发起调用后,快速返回类似 中 对象。

3. 弹性机制

事件驱动的松散耦合提供了组件在失败下,可以抓获完全隔离的上下文场景,作为消息封装,发送到下游组件。在具体编程时可以检查错误,比如:是否接收到,接收的命令是否可执行等,并决定如何应对。

Reactor简介

框架是 基于 思想实现的。它符合 规范 ( 是由 、 、 等公司发起的) 的一项技术。其名字有反应堆之意,反映了其背后的强大的性能

1. Reactive Programming

,中文称反应式编程。 是一种非阻塞事件驱动数据流的开发方案,使用函数式编程的概念来操作数据流,系统中某部分的数据变动后会自动更新其他部分,而且成本极低。

其最早是由微软提出并引入到 .NET 平台中,随后 ES6 也引入了类似的技术。在 Java 平台上,较早采用反应式编程技术的是 Netflix 公司开源的 RxJava 框架。Hystrix 就是以 RxJava 为基础开发的。

反应式编程其实并不神秘,通过与我们熟悉的迭代器模式对比,便可了解其基本思想:

上面表格的中的 那一列便代表反应式编程的 的使用方式。它其实是观察者模式的一种延伸。

如果将迭代器模式看作是拉模式,那观察者模式便是推模式

被订阅者主动推送数据给订阅者,触发 方法。异常和完成时触发另外两个方法。

被订阅者发生异常,则触发订阅者的方法进行异常捕获处理。

被订阅者每次推送都会触发一次方法。所有的推送完成且无异常时,方法将在最后触发一次。

如果 发布消息太快了,超过了 的处理速度,那怎么办?这就是 的由来。 框架需要提供背压机制,使得 能够控制消费消息的速度。

2. Reactive Streams

在 平台上, (开发了 )、 (开发了 、 )、 (开发了 、 )共同制定了一个被称为 项目(规范),用于制定反应式编程相关的规范以及接口。

由以下几个组件组成:

发布者:发布元素到订阅者

订阅者:消费元素

订阅:在发布者中,订阅被创建时,将与订阅者共享

处理器:发布者与订阅者之间处理数据

其主要的接口有这三个:

Publisher

Subscriber

Subcription

其中, 中便包含了上面表格提到的 、 、 这三个方法。对于 ,只需要理解其思想就可以,包括基本思想以及 等思想即可。

3. Reactor的主要模块

框架主要有两个主要的模块:

reactor-core

reactor-ipc

前者主要负责 相关的核心的实现,后者负责高性能网络通信的实现,目前是基于 实现的。

4. Reactor的核心类

在 中,经常使用的类并不是很多,主要有以下两个:

Mono

实现了 接口,代表 到 个元素的发布者

Flux

同样实现了 接口,代表 到 个元素的发表者。

Scheduler

代表背后驱动反应式流的调度器,通常由各种线程池实现。

5. WebFlux

引入的一个基于 而不是 的高性能的 框架 - ,但是使用方式并没有同传统的基于 的 有什么大的不同。

中 接口的示例:

最大的变化就是返回值从 所表示的一个对象变为 或 。

6. Reactive Streams、Reactor和WebFlux

上面介绍了反应式编程的一些概念。可能读者看到这里有些乱,梳理一下三者的关系:

是一套反应式编程标准规范

是基于 一套反应式编程框架

以 为基础,实现 领域的反应式编程框架

其实,对于业务开发人员来说,当编写反应式代码时,通常只会接触到 这个接口,对应到 便是 和 。

对于 和 这两个接口, 也有相应的实现。这些都是 和 这样的框架用到的。如果不开发中间件,开发人员是不会接触到的。

Reactor入门

接下来介绍一下 中 和 这两个类中的主要方法的使用。

如同 所引入的 一样, 的使用方式基本上也是分三步:

开始阶段的创建

中间阶段的处理

最终阶段的消费

只不过创建和消费可能是通过像 这样框架完成的(比如通过 中的 调用 接口,返回值便是一个 )。但我们还是需要基本了解这些阶段的开发方式。

1. 创建 Mono 和 Flux(开始阶段)

使用 编程的开始必然是先创建出 或 。有些时候不需要我们自己创建,而是实现例如 中的 或 得到一个 或 。

使用 WebFlux WebClient 调用 HTTP 接口

使用 ReactiveMongoRepository 查询 User

但有些时候,我们也需要主动地创建一个 或 。

普通的创建方式

这样的创建方式在什么时候用呢?一般是用在经过一系列非IO型操作之后,得到了一个对象。接下来要基于这个对象运用 进行高性能的 操作时,可以用这种方式将之前得到的对象转换为 或 。

文艺的创建方式

上面是通过一个同步调用得到的结果创建出 或 ,但有时需要从一个异步调用的结果创建出 或 。

如果这个异步方法返回一个 ,那可以基于这个 创建一个 :

如果这个异步调用不会返回 ,是有自己的回调方法,那怎么创建 呢?可以使用 方法:

在使用 之后, 已经不推荐使用,这里只是做演示。

2. 处理 Mono 和 Flux(中间阶段)

中间阶段的 和 的方法主要有 、 、 、 、 、 等。这些方法使用方法和 中的方法类似。

下面举几个 开发实际项目的问题,帮大家理解这些方法的使用场景:

问题一: map、flatMap 和 then 在什么时候使用

本段内容将涉及到如下类和方法:

方法

方法

方法

在 和中间环节的处理过程中,有三个有些类似的方法: 、 和 。这三个方法的使用频率很高。

传统的命令式编程

对应的反应式编程

从上面两段代码的对比就可以看出来 方法在其中起到的作用, 和 方法也有类似的作用。但这些方法之间的区别是什么呢?我们先来看看这三个方法的签名(以 为例):

flatMap(Function> transformer)

map(Function mapper)

then(Monoother)

then()

看上去是下一步的意思,但它只表示执行顺序的下一步,不表示下一步依赖于上一步。 方法的参数只是一个 ,无从接受上一步的执行结果。而 和 的参数都是一个 ,入参是上一步的执行结果。

flatMap() 和 map()

和 的区别在于, 中的入参 的返回值要求是一个 对象,而 的入参 只要求返回一个普通对象。在业务处理中常需要调用 或 中的方法,这些方法的返回值都是 (或 )。所以要将这些调用串联为一个整体链式调用,就必须使用 ,而不是 。

问题二:如何实现并发执行

本段内容将涉及到如下类和方法:

方法

并发执行是常见的一个需求。 虽然是一种异步编程方式,但是异步不代表就是并发并行的。

传统的命令式编程中,并发执行是通过线程池加 的方式实现的。

上面的代码虽然实现了异步调用,但 方法是阻塞的。在使用 开发有并发执行场景的反应式代码时,不能用上面的方式。

这时应该使用 和 中的 方法,以 为例,代码如下:

上述代码中,产生 和 的过程是并行的。比如,调用一个 接口的同时,执行一个数据库查询操作。这样就可以加快程序的执行。

但上述代码存在一个问题,就是 方法需要做强制类型转换。而强制类型转换是不安全的。好在 方法存在多种重载形式。除了最基本的形式以外,还有多种类型安全的形式:

对于不超过 个元素的合并操作,都有类型安全的 方法可选。以两个元素的合并为例,介绍一下使用方法:

上述代码中, 方法的参数是一个 ,表示一个二元数组,相应的还有 、 等。

对于两个元素的并发执行,也可以通过 方法直接将结果合并。方法是传递 实现合并算法

问题三:集合循环之后的汇聚

本段内容将涉及到如下类和方法:

方法

方法

另外一个稍微复杂的场景,对一个对象中的一个类型为集合类的( 、 )进行处理之后,再对原本的对象进行处理。使用迭代器模式的代码很容易编写:

当我们要用 风格的代码实现上述逻辑时,就不是那么简单了。这里会用到 的 方法。 方法的签名如下:

可以看出, 方法的功能是将一个聚合成一个 。

第一个参数: 返回值 中元素的初始值

第二个参数: 是一个 ,用来实现聚合操作的逻辑。对于泛型参数 中:

第一个 : 表示每次聚合操作之后的结果的类型,它作为 方法的第一个入参

第二个 : 表示集合中的每个元素的类型,它作为 方法的第二个入参

第三个 : 表示聚合操作的结果,它作为 方法的返回值

接下来看一下示例:

上面的示例代码中, 和 的类型相同。执行完上述代码之后, 方法会返回 。

3. 消费 Mono 和 Flux(结束阶段)

直接消费的 或 的方式就是调用 方法。如果在 接口中开发,直接返回 或 Flux 即可。 框架会完成最后的 输出工作。

小结

本文介绍了反应式编程的一些概念和 框架的基本用法,还介绍了如何用 解决一些稍微复杂一点的问题。 在 中有大量的应用,后面会给大家分享一些 实战系列的博客。

欢迎关注技术公众号: 零壹技术栈

本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180527G08Y6J00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券