响应式编程(Reactive Programming)
Posted 精锐e师
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了响应式编程(Reactive Programming)相关的知识,希望对你有一定的参考价值。
响应式编程(ReactiveProgramming)
1、背景介绍
1.1名词定义
百度百科的解释【1】:
中文名:响应式编程
外文名:Reactive Programming
简称:RP
释义:面向数据流和变化传播的编程范式
响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
-- Wikipedia【2】
In computing, reactive programming isan asynchronous programming paradigm concerned with data streams and thepropagation of change. This means that it becomes possible to express static(e.g. arrays) or dynamic (e.g. event emitters) data streams with ease via theemployed programming language(s), and that an inferred dependency within theassociated execution model exists, which facilitates the automatic propagationof the change involved with data flow.
在计算机领域,响应式编程是一个专注于数据流和变化传递的异步编程范式。这意味着可以使用编程语言很容易地表示静态(例如数组)或动态(例如事件发射器)数据流,并且在关联的执行模型中,存在着可推断的依赖关系,这个关系的存在有利于自动传播与数据流有关的更改。
1.2目的
响应式编程最初是为了简化交互式用户界面的创建和实时系统动画的绘制而提出来的一种方法,但是本质是一种通用的编程范式。【1】
2、响应式编程的特性【3】
先来看一个我们经常使用的一款堪称“响应式典范”的强大的生产力工具——电子表格。
举个简单的例子,某电商网站正在搞促销活动,任何单品都可以参加“满199减40”的活动,而且“满500包邮”。吃货小明有选择障碍(当然主要原因还是一个字:穷),他有个习惯,就是先在Excel上根据预算算好自己要买的东西:
相信大家都用过Excel中的公式,这是一个统计购物车商品和订单应付金额的表格,其中涉及到一些公式:
上图中蓝色的线是公式的引用关系,从中可以看出,“商品金额”是通过“单价x数量”得到的,“满199减40”会判断该商品金额是否满199并根据情况减掉40,右侧“订单总金额”是“满199减40”这一列的和,“邮费”会根据订单总金额计算,“最终应付款”就是订单总金额加上邮费。
2.1 变化传递(propagation of change)
为什么说电子表格软件是“响应式典范”呢,因为“单价”和“数量”的任何变动,都会被引用(“监听”)它的单元格实时更新计算结果,如果还有图表或数据透视图引用了这块数据,那么也会相应变化,做到了实时响应。变化的时候甚至还有动画效果,用户体验一级棒!
这是响应式的核心特点之一:变化传递(propagation of change)。一个单元格变化之后,会像多米诺骨牌一样,导致直接和间接引用它的其他单元格均发生相应变化。
拿上面购物的例子来说,
假设购物车管理和订单付款是两个不同的类——Cart
和Invoice
。我们的代码是这样的:
Product.java(假设商品有两个属性name
和price
)
假设数据流有操作的商品product
和变化个数quantity
两个属性:
2.2 数据流(data stream)
这些数据/事件在响应式编程里会以数据流的形式发出。
我们再观察一下购物车,这里有若干商品,小明每次往购物车里添加或移除一种商品,或调整商品的购买数量,这种事件都会像过电一样流过这由公式串起来的多米诺骨牌一次。这一次一次的操作事件连起来就是一串数据流(data stream),如果我们能够及时对数据流的每一个事件做出响应,会有效提高系统的响应水平。这是响应式的另一个核心特点:基于数据流(data stream)。
如下图是小明选购商品的过程,为了既不超预算,又能省邮费,有时加有时减:
这一次一次的操作就构成了一串数据流。Invoice模块中的代码可能是这样:
其中,cart.eventStream()
是要监听的购物车的操作事件数据流,listenOn
方法能够对数据流中到来的元素依次进行处理。
2.3 声明式(declarative)
我们再到listenOn
方法去看一下:
Invoice模块中,上边的一串公式被组装成如下的伪代码:
1. cartEventStream是数据流,DataStream是某种数据流类型,可以暂时想象成类似在Java 8版本增加的对数据流进行处理的Stream API(下节会说到为啥不用Java Stream)。
2. map方法用于对数据流中的元素进行映射,比如第一个将cartEvent中的商品价格和数量拿到,然后算出本次操作的金额;第二个判断是否能享受“满199减40”的活动。
这是一种“声明式(declarative)”的编程范式。通过四个串起来的map
调用,我们先声明好了对于数据流“将会”进行什么样的处理,当有数据流过来时,就会按照声明好的处理流程逐个进行处理。
声明式编程范式的威力在于以不变应万变。无论到来的元素是什么,计算逻辑是不变的,从而形成了一种对计算逻辑的“绑定”。
再举个简单的例子方便理解:
这个时候,b是多少呢?在Java以及多数语言中,b的结果是2,第二次对a的赋值并不会影响b的值。
假设Java引入了一种新的赋值方式:=,表示一种对a的绑定关系,如
由于b保存的不是某次计算的值,而是针对a的一种绑定关系,所以b能够随时根据a的值的变化而变化,这时候b==3,我们就可以说:=是一种声明式赋值方式。而普通的=是一种命令式赋值方式。事实上,我们绝大多数的开发都是命令式的,如果需要用命令式编程表达类似上边的这种绑定关系,在每次a发生变化并需要拿到b的时候都得执行b= a + 1来更新b的值。
不过命令式和声明式本身并无高低之分,只是声明式比较适合基于流的处理方式。这是响应式的第三个核心特点:声明式(declarative)。结合“变化传递”的特点,声明式能够让基于数据流的开发更加友好。
2.4 总结
总结起来,响应式编程(reactive programming)是一种基于数据流(data stream)和变化传递(propagation of change)的声明式(declarative)的编程范式。
响应式编程的“变化传递”就相当于果汁流水线的管道;在入口放进橙子,出来的就是橙汁;放西瓜,出来的就是西瓜汁,橙子和西瓜、以及机器中的果肉果汁以及残渣等,都是流动的“数据流”;管道的图纸是用“声明式”的语言表示的。
在写响应式代码的时候,心里一定要默念着,我所做的事情就是建立一条数据通道,在通道上指定的位置插入适合的逻辑处理代码。同时还要切记,主线程执行完时,只是建立了通道,并没有数据。【4】
即时响应也意味着可以快速的检测到问题并且有效的对问题作出反应(回压),并进行处理,从而实现负载管理、弹性以及流量控制。
这种编程范式如何让Web应用更加“reactive”呢?【3】
我们设想这样一种场景,我们从底层数据库驱动,经过持久层、服务层、MVC层中的model,到用户的前端界面的元素,全部都采用声明式的编程范式,从而搭建一条能够传递变化的管道,这样我们只要更新一下数据库中的数据,用户的界面上就相应的发生变化,岂不美哉?尤其重要的是,一处发生变化,我们不需要各种命令式的调用来传递这种变化,而是由搭建好的“流水线”自动传递。
这种场景用在哪呢?比如一个日志监控系统,我们的前端页面将不再需要通过“命令式”的轮询的方式不断向服务器请求数据然后进行更新,而是在建立好通道之后,数据流从系统源源不断流向页面,从而展现实时的指标变化曲线;再比如一个社交平台,朋友的动态、点赞和留言不是手动刷出来的,而是当后台数据变化的时候自动体现到界面上的。
3、响应式编程优点及应用
3.1网上摘录了文章对响应式编程的评价
响应式编程就是与异步数据流交互的编程范式。【4】
响应式是这样的一个思路:数据流无处不在,任何东西都可以成为一个数据流,例如变量、用户输入、属性、缓存、数据结构等等。举个例子,你可以把你的微博订阅功能想象成跟点击事件一样的数据流,你可以监听这样的数据流,并作出相应的反应。【4】
最重要的是,你会拥有一些令人惊艳的函数去结合、创建和过滤任何一组数据流。这就是"函数式编程"的魔力所在。一个数据流可以作为另一个数据流的输入,甚至多个数据流也可以作为另一个数据流的输入。你可以合并两个数据流,也可以过滤一个数据流得到另一个只包含你感兴趣的事件的数据流,还可以映射一个数据流的值到一个新的数据流里。【4】
响应式编程可以加深你代码抽象的程度,让你可以更专注于定义与事件相互依赖的业务逻辑,而不是把大量精力放在实现细节上,同时,使用响应式编程还能让你的代码变得更加简洁。【4】
对于现在流行的WebApp和MobileApp,他们的UI事件与数据频繁地产生交互,在开发这些应用时使用响应式编程的优点将更加明显。十年前,web页面的交互是通过提交一个很长的表单数据到后端,然后做一些简单的前端渲染操作。而现在的APP则演变的更具有实时性:仅仅修改一个单独的表单域就能自动触发保存到后端的代码,就像某个用户对一些内容点了赞,就能够实时反映到其他已连接的用户一样。【4】
3.2日常接触到的响应式编程
1、Java设计模式中的观察者模式:基于异步非阻塞
2、Flink基于事件驱动的处理模式:基于事件流
3.3响应式编程的编程库或产品
1、在Java 9中提供了响应式编程的规范,已经集成到java.util.concurrent.Flow类之下。
2、Spring 5 新增全新的reactive web框架:WebFlux。
3、RxJava:一个支持编写异步和基于事件的程序的库。
4、Dubbo框架的升级版本Dubbo 3.0支持Reactive Stream的rx接口。
4、行内对响应式编程的应用——反应式架构【5】
4.1、对业界的调研
Rx(RxJava/RxJS...) |
Vert.x |
Project Reactive |
Akka stream |
|
特点 |
出现早,比较稳定用户较多全栈:客户端+服务端+前端 |
出现早,比较稳定用户较多 |
Spring加持虽然出现时间不是很长,但是后续可能会爆发 |
基于scala,相对小众 |
稳定性 |
好 |
好 |
一般 |
好 |
普及性 |
好 |
好 |
一般 |
一般 |
4.2、行内的架构规划
4.3、适用场景
1、多个系统间的多次远程调用带来的分布式问题,尤其在长任务场景中,响应式架构有异步和背压的特性,效果相对明显。
2、微服务场景下,面对成千上万的微服务接口,错综复杂的调用链,使用反应式架构可以较好的规避可能导致雪崩的风险。
3、系统性能遇到瓶颈,无法或者不方便通过水平扩展来解决,此时考虑采用反应式架构,提升系统可承载的能力。
4、对系统有较高的可用性要求的场景,采用反应式架构可以较好的保证系统实时响应能力,避免系统崩溃。
5、主进程需要同时调用较多子任务的场景下,可以使用反应式编程让各个子任务并行执行
6、如果下一个任务基于上一个任务的结果的这种类似流式编程,可以使用反应式编程,反应式编程天生基于数据流和变化传递进行开发
5、参考内容
【1】:百度百科词条:响应式编程
【2】:Wikipedia:Reactive Programming
【3】:CSDN的文章:https://blog.csdn.net/get_set/article/details/79455258
【4】:博客园的文章:https://www.cnblogs.com/android-blogs/p/5586395.html
【5】:云计算实验室的调研材料:《反应式架构简介》
以上是关于响应式编程(Reactive Programming)的主要内容,如果未能解决你的问题,请参考以下文章
5 分钟理解什么是响应式编程 Reactive Programming
函数式响应式编程 - Functional Reactive Programming
浅析Java响应式编程(Reactive Programming)