本文作者知秋,节选自《Java编程方法论:响应式Spring Reactor 3设计与实现》一书。

-------

最近几年,随着Go、Node 等新语言、新技术的出现,Java 作为服务器端开发语言老大的地位受到了不小的挑战。虽然Java 的市场地位在短时间内并不会发生改变,但Java 社区还是将挑战视为机遇,并努力、不断地提高自身应对高并发服务器端开发场景的能力。

为了应对高并发服务器端开发场景,在2009 年,微软提出了一个更优雅地实现异步编程的方式—— Reactive Programming ,我们称之为响应式编程

随后,各语言很快跟进,都拥有了属于自己的响应式编程实现。比如,JavaScript 语言就在ES6 中通过Promise 机制引入了类似的异步编程方式。同时,Java 社区也在快速发展,Netflix 和LightBend 公司提供了RxJava 和Akka Stream 等技术,使得Java 平台也有了能够实现响应式编程的框架。

当下,我们通过Mina 和Netty 这样的NIO 框架其实就能完成高并发下的服务器端开发任务,但这样的技术只掌握在少数高级开发人员手中,因为它们难度较大,并不适合大部分普通开发者。

虽然目前已经有不少公司在实践响应式编程,但整体来说,其应用范围依旧不大。出现这种情况的原因在于当下缺少简单、易用的技术,这些技术需要能使响应式编程更加普及,并做到如同Spring MVC 一样结合Spring 提供的服务对各种技术进行整合。

在2017 年9 月28 日,Spring 5 正式发布。Spring 5 发布最大的意义在于,它将响应式编程技术的普及向前推进了一大步。而同时,作为在背后支持Spring 5 响应式编程的框架Spring Reactor,也进入了里程碑式的3.1.0 版本。

响应式编程到底是什么?

在现实生活中,当我们听到有人喊我们名字的时候,会对其进行响应,也就是说,我们是基于事件驱动模式来进行编程的。所以这个过程其实就是下发产生的事件,然后我们作为消费者对下发事件进行一系列的消费。

从这个角度来说,对整个代码的设计应该是针对消费者来进行的。比如,看电影,有些画面我们不想看,那就闭上眼睛;有些声音不想听,那就捂上耳朵。其实这就是对消费者的增强包装,我们把复杂的逻辑拆分开,然后将其分割成一个个小任务进行封装,于是就有了诸如filter、map、skip、limit 等操作。

01

并发与并行的关系

可以说,并发很好地利用了CPU 时间片的特性,也就是操作系统选择并运行一个任务,接着在下一个时间片内运行另一个任务,并把前一个任务设置成等待状态。其实并发并不意味着并行。

具体列举下面几种情况。

① 有时候,多线程执行会提高应用程序的性能,而有时候反而会降低应用程序的性能。这在 JDK 中Stream API 的使用上体现得很明显,如果任务量很小,而我们又使用了并行流,反而降低了应用程序的性能。

② 在多线程编程中,可能会同时开启或者关闭多个线程,这样会产生很大的性能开销, 也降低了应用程序的性能。

③ 当线程同时处于等待I/O 的过程中时,并发可能会阻塞CPU 资源,其后果不仅是用户长时间等待,而且会浪费CPU 的计算资源。

④ 如果几个线程共享了一个数据,情况就会变得有些复杂。我们需要考虑数据在各个线程中的状态是否一致。为了达到数据一致的目的,很可能会使用synchronized 或者lock 相关操作。

现在,你对并发有一定的了解了吧。并发很好,但并不一定会实现并行。并行是在多核CPU 上同一时间运行多个任务或者一个任务分为多块同时执行(如ForkJoin)。单核CPU 的话,就不要考虑并行了。

补充一点,实际上多线程就意味着并发,但是并行只发生在这些线程在同一时间调度、分配到不同CPU 上执行的情况下。也就是说,并行是并发的一种特定形式。一个任务里往往会产生很多元素,这些元素在不参与操作的情况下大都只能处于当前线程中,这时我们可以对其进行ForkJoin,但这对很多程序员来讲有时候很不好操作、控制,上手难度有些大。这时如果用响应式编程,就可以简单地通过所提供的调度API 轻松做到事件元素的下发、分配,其内部会将每个元素包装成一个任务并提交到线程池中,我们可以根据任务是计算型的还是I/O 型的来选择相应的线程池。

在这里,需要强调一下,线程只是一个对象,不要把它想象成CPU 中的某一个执行核心,这是很多人都在犯的错,CPU 时间片会切换执行这些线程。

02

如何理解响应式编程中的背压

背压,由Back Pressure 翻译得到,从英文字面意思讲,称之为回压可能更合适。首先解释一下回压,它就好比用吸管喝饮料,将吸管内的气体吸掉,吸管内形成低压,进而形成饮料至吸管方向的吸力,此吸力将饮料吸进人嘴里。我们常说人往高处走,水往低处流,水之所以会出现这种现象,其实是重力所致。而现在吸管下方的水上升进入人的口中,说明出现了下游指向上游的逆向压力,而且这个逆向压力大于重力,可以称这种情况为背压。这是一个很直观的词,向后的、往回的压力——Back Pressure。

放在程序中,也就是在数据流从上游源生产者向下游消费者传输的过程中,若上游源生产速度大于下游消费者消费速度,那么可以将下游想象成一个容器,它处理不了这些数据,然后数据就会从容器中溢出,也就出现了类似于吸管例子中的情况。现在,我们要做的事情就是为这个场景提供解决方案,该解决方案被称为背压机制。

为了更好地解决背压带来的问题,我们回到现实中看一个事物——大坝。在发洪水期间,下游没办法一下子消耗那么多水,大坝此时的作用就是拦截洪水,并根据下游的消耗情况酌情排放,也就是说,背压机制应该放在连接元素生产者和消费者的地方,即它是生产者和消费者的衔接者。然后,根据上面对大坝的描述,背压机制应该具有承载元素的能力,也就是它必须是一个容器,而且其存储与下发的元素应该有先后顺序,那么这里使用队列是最适合的了。背压机制仅起承载作用是不够的,正因为上游进行了承压,所以下游可以按需请求元素,也可以在中间根据实际情况进行限流,以此上下游共同实现了背压机制。在本书后续内容及相关的配套视频中会介绍背压的相关API。

03

Reactor 与RxJava 的对比

关于响应式编程,我写的《Java 编程方法论:响应式RxJava 与代码设计实战》一书已经出版,那么Reactor 与RxJava 又有什么区别呢?首先我要明确地告诉你,如果你使用的是Java 8+,那么推荐使用Reactor 3,而如果你使用的还是Java 6+或函数需要做异常检查,那么推荐使用RxJava 2。

从上图可以看到,RxJava 2 和Reactor 共用了一套接口API 标准Reactive Streams Commons,这也说明它们的最终目的是一致的,而且API 具有通用性,这样也降低了学习成本。

下面再来回顾一下RxJava

迄今为止,RxJava 发行版主要分三大版本RxJava 3、RxJava 2 和RxJava 1。与RxJava 1 不同,RxJava 3、RxJava 2 直接通过新添加的Flowable 类型来实现Publisher 的接口定义(RxJava 3 与RxJava 2 并没有太多区别,故这里只介绍RxJava 2)。同时,RxJava 2 依然保留了RxJava 1 中的Observable、Completable 和Single,并引入了支持Optional 的Single 升级版——Maybe 类型。RxJava 1 中的Observable 不支持RxJava 2 中的背压机制,背压机制是Flowable 的专有功能,不过Observable 内部提供了可转换API。需要注意的是,Observable 实现的是RxJava 2 中自定义的ObservableSource 接口。

在Reactor 中,可以发现Mono 和Flux 两种类型都实现了Publisher 接口,同时两者皆实现了背压机制。Flux 可以对标RxJava 2 中的Flowable 类型,而Mono 可以被理解为RxJava 2 中对Single 的背压加强版。后续,我们会进行更深入的讲解。

同样,下面再来了解一下Reactor 与RxJava 的不同之处。

  • 为了兼容 Java 1.6+ ,RxJava 不得不自行定义了一些函数式接口,可以参考io.reactivex.functions 下的接口定义。而Reactor 3 则是基于JDK 中提供的java.util.function 来设计实现的。
  • 可以很轻松地从java.util.stream.Stream 转换为Flux,也可以很轻松地由后者转换为前者。
  • 同样,可以很轻松地实现CompletableFuture 与Mono 之间的互相转换,也可以轻松而安全地基于Optional 类型的元素创建Mono。
  • Reactor 3 可以更好地服务于Spring Framework 5,也更适应最新版本的JDK。

最后,我们再简单介绍一下上图中的几个部分。

Core 是我们主要研究的库,是Reactor 的核心实现库。其作用与RxJava 2 的核心实现的作用是一样的,本书主要介绍reactor-core 模块。

IPC 可以认为它是针对encode、decode、send(unicast、multicast 或request/response )及服务连接而设计的支持背压的组件。IPC 支持Kafka、Netty 及Aeron。

Addons 其中包括reactor-adapter、reactor-logback 和reactor-extra。reactor-adapter 可以说是连接RxJava 1/2 中Observable、Completable、Flowable、Single、Maybe、Scheduler 的桥梁,可以方便地与Reactor 3 进行转换操作。同样,这个库对于Swing/SWT Scheduler、Akka Scheduler 也做了针对性适配。reactor-logback 用于支持Reactor Core 异步处理Logback 方面的功能。reactor-extra 为数字类型的Flux 源提供了很多数学运算的操作。

Reactive Streams Commons 是RxJava 2 和Reactor 共用的一套接口API 标准。

(完)

----------

作者简介

作者知秋,本名李飞飞,simviso团队创始人,曾长期致力于基础代码库研发工作,对JDK、Spring、RxJava、Spring Reactor、Netty、Reactor-Netty、RSocket等有深刻的研究和独到的见解,并以此打造“Java编程方法论系列丛书”。一直通过博客与视频平台bilibili(B站),结合自己的经验进行大量源码解读分享。现在主要致力于带领simviso的小伙伴引进、翻译国外知名高校计算机科学相关课程及国外知名Java开发者的技术分享内容。

图书推荐

《Java编程方法论:响应式Spring Reactor 3设计与实现》

知秋 著

  • 包含配套视频和源码
  • 一本成体系的、传授编程思想和技巧的响应式编程学习图书

本书主要专注于解读Spring Reactor 3的代码设计与实现,以揭示其中的设计精髓,帮助大家灵活运用及提升代码设计思维。本书是学习响应式编程非常好的参考资料,是成体系的、传授编程思想和技巧的图书。

一文读懂响应式编程到底是什么?相关推荐

  1. 一文搞懂: 响应式编程是什么?

    文章目录 导言 响应式编程是什么? 响应式宣言 响应式系统应该具有什么特性 什么是弹性? 回弹性? 消息驱动还是事件驱动? 响应式编程相关讨论 为什么使用观察者模式 什么是背压(backPressur ...

  2. 一文搞懂 响应式编程是什么

    文章目录 导言 响应式编程是什么? 响应式宣言 响应式系统应该具有什么特性 什么是弹性? 回弹性? 消息驱动还是事件驱动? 响应式编程相关讨论 为什么使用观察者模式 什么是背压(backPressur ...

  3. 赠书:响应式编程到底是什么?

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 最近几年,随着Go.Node 等新语言.新技术的出现,J ...

  4. 阿里淘宝一直在推的响应式编程到底是个什么鬼?

    随着这些年智能手机的发展和普及,我们的服务器端要应对日益增长的巨大流量. 从开发的角度来看,这就要求我们必须设计出高扩展性和高可用性的程序,以确保能够适应日益增长的请求所带来的压力. 而从使用者的角度 ...

  5. 一文读懂服务式办公室、共享办公室、联合办公空间、开放式工位的关系

    最近几年,很多企业在找合适的办公室的时候应该都听过"服务式办公室"."共享办公室"."联合办公空间"."开放式工位"这四 ...

  6. 一文读懂 shell 脚本编程

    一.shell 脚本基础知识 shell脚本是一个文件,里面存放的是特定格式的指令,系统可以使用脚本解析器翻译或解析指令并执行(无需编译).shell脚本的本质是 shell命令的有序集合 shell ...

  7. 一文读懂什么是编程!

    编程,俗称写代码,或者写程序,是指编写计算机程序员,让计算机按照编程者的思维去执行一系列指令的行为. 编程可以做什么? 一.编程可以控制小到计算机.手机.智能手表.智能汽车.智能家电,大到航天飞机.登 ...

  8. 网络编程懒人入门(十一):一文读懂什么是IPv6

    本文同时发布于"即时通讯技术圈"公众号,链接是:https://mp.weixin.qq.com/s/cS5xB2DrjF52rmz6EGVJ6A. 本文参考了公众号鲜枣课堂的&q ...

  9. 阿里专家杜万:Java响应式编程,一文全面解读

    本篇文章来自于2018年12月22日举办的<阿里云栖开发者沙龙-Java技术专场>,杜万专家是该专场第四位演讲的嘉宾,本篇文章是根据杜万专家在<阿里云栖开发者沙龙-Java技术专场& ...

  10. 网络编程之一文读懂什么是IPv6

    网络编程之一文读懂什么是IPv6 复习一下什么是IPv4? IPv6又是什么? 为什么要使用IPv6? IPv6会带给我们什么? IPv6的优点这么多,为什么之前普及却这么慢? NAT这类技术,让IP ...

最新文章

  1. mysql grant all详解_grant all赋权后mysql.user表权限字段还是N,但能登录和新建表
  2. 到底什么时候用指针或是引用
  3. 使用Nomad构建弹性基础架构:计划和自我修复
  4. 民生银行马坡数据中心基础设施Uptime Mamp;O运营管理体系建立与实施
  5. php读取js验证码,js实现获取短信验证码实例
  6. 【编译原理】第二章练习题
  7. this的用法添加样式给tr或者div
  8. 阿里云助力1药网开辟疫情防控“第二战场”
  9. 10100的素数c语言程序,C语言基础题目及代码.doc
  10. 树莓派竟出微控制器了!Raspberry Pi Pico 只需 4 美元!
  11. [转载] python学习笔记——@property装饰器
  12. 无监督学习︱GAN 在 NLP 中遇到瓶颈+稀疏编码自学习+对偶学习
  13. 移动应用android简单播放器,打造移动掌上影院 Android播放器横评
  14. mybatis单元测试(无需启动容器)
  15. FIO压测-SSDvsHDD
  16. JAVA-jdk8的API文件下载
  17. cass等距离等分线段的命令键_CAD等分线段指令是什么?
  18. 外卖券-外卖优惠券-原生微信小程序
  19. 构建基于 MCU 安全物联网系统
  20. 搬运--domoticz的 MQTT API接口 函数英文

热门文章

  1. 可遇不可求的Question之error: Failed dependencies: MySQLconflicts 错误篇
  2. Java数组3(2015-8-27)
  3. ArcGIS for Android地图控件的5大常见操作
  4. LINUX下查看CPU使用率的命令[Z]
  5. 「2013-9-14」Change Remote Desktop Port
  6. GridView中 选中checkbox,在上下页 保存选中的值
  7. MS SQL中查看text,ntext,image类型数据
  8. 在SqlMapConfig.xml 中typeAliases与properties的关系
  9. 数据结构与算法(三)-线性表之静态链表
  10. 记一次%转义引发的血案