Reactive(2) 响应式流与制奶厂业务
再谈响应式
在前一篇文章从Reactive编程到“好莱坞”中,谈到了响应式的一些概念,讲的有些发散。 但仅仅还是停留在概念的层面,对于实战性的东西并没有涉及。
所以大家看了后,或许还是有些不痛不痒。
响应式编程强调的是异步化、面向流的处理方式,这两者也并非凭空生出,而是从大量的技术实践中总结提炼出来的概念,就比如:
我们谈异步化,容易联想到 Java 异步IO(Asynchronized IO),而且习惯于将其和 BIO、NIO等概念来做对比。 殊不知,老早出现的 Swing 框架(Java UI)就已经将异步化思维玩的很溜了,不信的可以看看其内部 Observer模式(观察者)的实现。
我们谈流式处理,容易联想到 时下当红的 Flink框架。 但几乎所有的大数据分析、批处理应用都是基于流式进行处理的,比如 ETL,甚至是一个最简单的 Map Reduce 作业。
为什么Web后端开发的,对 Reactive 没有感觉

除了前端,Reactive 概念在大数据领域的应用其实非常的广泛了。 但是对于大多数做 Web 后端开发的人来说或许普及程度并不高,以笔者自身的感受是,码了这么些年头,除了做好代码分层之外,似乎也没有见到 Reactive可以发挥重大作用的地方。 原因就在于,在Web 后端开发领域基本是依托 HTTP协议机制实现的,这是一个相当简单的 请求 -> 应答 交互模式,客户端在发送请求后,会一直等待结果返回,也就是结果的通知是由客户端主动获取而非异步通知的,因此并不是 Reactive 的风格。 但这已经是符合用户一贯的使用方式了,绝大多数情况下并不需要做什么样的变化,此时我们对响应式的感知并不深刻。
更符合Reactive 的另外一个场景是 富客户端(Rich Application),假设在需要大量复杂的前端交互的场景下,我们可以选择将一些逻辑放在前端代码中实现。 此时的 Web 交互就不再是整个页面的刷新,而是演变为客户端与服务端的"实时"双向通讯,这类应用也比较普遍了,比如基于 WebSocket 实现的 聊天应用、小游戏等等。
浅显的从趋势上看, Reactive 的前景还是很明朗的,这里并不是说因为现在多数流行的编程语言中都有它的影子(比如提供了Rx风格的框架)。
而是未来的大数据处理、实时流计算会成为主流,这是环境决定的。 而这时 Reactive 这种"面向流"的编程模式无疑是很合适的。
Java 9 支持的 Reactive Stream
Java 平台直到 JDK 9 才提供了对于 Reactive 的完整支持,而在此之前的JDK版本中,也以及存在一些有关联性的API,比如:
- Future 和 CompletableFuture接口,用于实现异步计算。 后者较前者则是完善了异步结果通知、任务串行等特性。
- Stream 接口,可以将传统的集合转换为"流"的方式进行处理,比如迭代、映射转换。
这些关联性API 并不是完整的 Reactive,Java 9所支持的 Reactive Stream API 来自于2013年的响应式流规范(Reactive Stream Specification)。
https://www.reactive-streams.org/
基于这个规范中主要定义了下面几个接口:
Java的响应式流接口统一定义在 java.util.concurrent.Flow接口
Publisher
即数据的发布者。 Publisher 接口定义了一个subscribe方法,用于添加订阅者:Subscriber
指数据的订阅者。 Subscriber 接口定义了4个方法,用于针对不同的事件作出响应。
首先,在subscribe方法调用成功后,Subscriber的 onSubscribe(Subscription s) 方法会被触发(Subscription 表示当前的订阅关系)。
此后,正常可以继续调用 Subscription 的 request(long n) 方法来向发布者请求数据,n是指最大的数据条目数。
发布者会产生3种不同的消息,分别对应到 Subscriber 的3个回调方法:
数据消息:对应 onNext 方法,表示发布者产生的数据。
错误消息:对应 onError 方法,表示发布者产生了错误。
结束消息:对应 onComplete 方法,表示发布者已经完成了所有数据的发布。
在上面的3种通知中,错误、结束消息都表示当前的流已经到达了终点,后面不再会有消息产生。
Subscription
Subscription 表示的是一个订阅关系。 可以通过该对象请求数据(request方法),或者取消订阅(cancel方法)。Processor
Processor 表示的一种特殊的对象,既是生产者,又是订阅者。
负压的支持
负压是响应式流定义的一种重要的能力,在上述的接口中,实质上已经提供了负压的支持。
Publisher 只有在收到请求之后,才会产生数据。 这就保证了 Subscriber 可以根据自己的处理能力,确定要向 Publisher 请求的数据量,以此保证自身不会被冲垮。
范例
下面,以一个简单的代码示例来演示 Reactive Stream API 是如何使用的。

以某一个制奶厂为例,为了提高营收,工厂推出了一个厂家直销的业务。 顾客可以直接向厂方订购一定天数的奶制品,每天则是由工厂的服务人员送奶上门。
为了模拟这个场景,我们实现的代码如下:
- 制奶厂,一个Publisher实现:
public class MilkFactory extends SubmissionPublisher<String> {
private final ScheduledFuture<?> periodicTask;
private final ScheduledExecutorService scheduler;
private static final List<String> milks = Arrays.asList("益力多", "酸牛奶", "原味奶", "低脂蛋奶", "羊奶", "甜牛奶");
public MilkFactory() {
super();
//初始化定时器
scheduler = new ScheduledThreadPoolExecutor(1);
//每一天生产完牛奶并推送给消费者
periodicTask = scheduler.scheduleAtFixedRate(
() -> submit(produceMilk()), 0, 1, TimeUnit.SECONDS);
}
//随机生产牛奶
private String produceMilk() {
return milks.get((int) (Math.random() * milks.size()));
}
//关闭流
public void close() {
periodicTask.cancel(false);
scheduler.shutdown();
super.close();
}
}
MilkFactory 集成自SubmissionPublisher(一个提供缓冲的Publisher实现),其内部会启动一个定时器,用于模拟每天给用户发放生产的牛奶。
通过submit()方法可以将数据推送给用户。
- 顾客,一个Subscriber实现:
public class MilkCustomer implements Flow.Subscriber<String> {
private Flow.Subscription subscription;
private AtomicInteger available = new AtomicInteger(0);
private int dayCount;
public MilkCustomer(int dayCount) {
this.dayCount = dayCount;
}
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
//设置总量
available.set(dayCount);
//第一天
subscription.request(1);
}
@Override
public void onNext(String milk) {
System.out.println("今天的牛奶到了: " + milk);
//如果还有存量,继续请求
if(available.decrementAndGet() > 0){
subscription.request(1);
}else{
System.out.println("牛奶套餐已经派完,欢迎继续订购");
this.subscription.cancel();
}
}
@Override
public void onError(Throwable t) {
t.printStackTrace();
}
@Override
public void onComplete() {
System.out.println("closed.");
}
}
MilkCustomer 接受一个dayCount入参,即表示订购的数量,在首次订阅时会请求第一天的奶品,此后则每次收到到奶品后再请求下一天的,直到将总量消费完。
- 测试程序
执行下面的代码:
MilkFactory factory = new MilkFactory();
//订阅1周
MilkCustomer customer = new MilkCustomer(7);
factory.subscribe(customer);
输出:
今天的牛奶到了: 酸牛奶
今天的牛奶到了: 羊奶
今天的牛奶到了: 原味奶
牛奶套餐已经派完,欢迎继续订购
小结
在上例中,我们使用 Java 提供的 Reactive Stream API 实现了一个"送奶上门" 的业务流。
整个过程相对是比较简单的,最关键的地方就在于对流式处理以及订阅关系的理解。 然而目前的 Reactive 实现还没有完全的统一,比如 Spring WebFlux(SpringBoot 2支持) 仍然是基于 Reactor 私有API而不是 Reactive Stream API 来构建的,后面有机会再做下介绍。
扩展阅读
关于Future和CompletableFuture的区别
https://juejin.im/post/5adbf8226fb9a07aac240a67
Reactive(2) 响应式流与制奶厂业务的更多相关文章
- JVM平台上的响应式流(Reactive Streams)规范
// Reactive Streams // 响应式流是一个倡议,用来为具有非阻塞后压的异步流处理提供一个标准.大家努力的目标集中在运行时环境(JVM和JavaScript)和网络协议上. 注:响应式 ...
- JVM上的响应式流 — Reactor简介
强烈建议先阅读下JVM平台上的响应式流(Reactive Streams)规范,如果没读过的话. 官方文档:https://projectreactor.io/. 响应式编程 作为响应式编程方向上的第 ...
- 响应式流API的构建基础
下面三个重要的概念是响应式流API的构建基础: 发布者是事件的发送方,可以向它订阅. 订阅者是事件订阅方. 订阅将发布者和订阅者联系起来,使订阅者可以向发布者发送信号. http://www.info ...
- reactive stream: 响应式编程
既然 Reactive Stream 和 Java 8 引入的 Stream 都叫做流,它们之间有什么关系呢?有一点关系,Java 8 的 Stream 主要关注在流的过滤,映射,合并,而 Reac ...
- Reactive(3)5分钟理解 SpringBoot 响应式的核心-Reactor
目录 一.前言 二. Mono 与 Flux 构造器 三. 流计算 1. 缓冲 2. 过滤/提取 3. 转换 4. 合并 5. 合流 6. 累积 四.异常处理 五.线程调度 小结 参考阅读 一.前言 ...
- Reactive 理解 SpringBoot 响应式的核心-Reactor
Reactive 理解 SpringBoot 响应式的核心-Reactor bestcoding 2020-02-23 17:26:43 一.前言 关于 响应式 Reactive,前面的两篇文章谈了不 ...
- 5分钟理解 SpringBoot 响应式的核心-Reactor
目录 一.前言 二. Mono 与 Flux 构造器 三. 流计算 1. 缓冲 2. 过滤/提取 3. 转换 4. 合并 5. 合流 6. 累积 四.异常处理 五.线程调度 小结 参考阅读 一.前言 ...
- Reactive(1) 从响应式编程到"好莱坞"
目录 概念 面向流设计 异步化 响应式宣言 参考文档 概念 Reactive Programming(响应式编程)已经不是一个新东西了. 关于 Reactive 其实是一个泛化的概念,由于很抽象,一些 ...
- 函数式响应式编程 - Functional Reactive Programming
我们略过概念,直接看函数式响应式编程解决了什么问题. 从下面这个例子展开: 两个密码输入框,一个提交按钮. 密码.确认密码都填写并一致,允许提交:不一致提示错误. HTML 如下: <input ...
随机推荐
- threeJS创建mesh,创建平面,设置mesh的平移,旋转、缩放、自传、透明度、拉伸
这个小案例是当初我在学习的时候,小的一个小案例,代码还需要进一步优化:还请谅解~~:主要用到了threeJS创建mesh,创建平面,设置mesh的平移,旋转.缩放.自传.透明度.拉伸等这些小功能: 采 ...
- 脱壳0-FSG壳-详细流程
目录 @ 1 拿到当前加壳程序,用exeinfo/PeID 看一下信息 可以看出是很老的壳FSG. 分析: Entry Point : 000000154,熟悉PE结构的知道,入口点(代码)揉进P ...
- [解决方案]IIS配置后报错404,500,502等系列问题
很多时候刚部署完服务器的IIS以后,第一次部署站点都会出现形形色色,各种各样的报错问题,但这些问题大同小异,我这里就给大家提供的解决的方案,以达到以不变应万变的效果 目的:让大家学会处理类似问题的方法 ...
- java中dao层和service层的区别是什么
dao层中已经有操作数据库的方法了,为什么还要service层去封装?有什么好处? tanghui12321 | 浏览 131990 次 我有更好的答案 推荐于2017-10-06 18:44:5 ...
- Linux命令之文件相关
cd 绝对路径相对路径 --转到对应目录 touch-a --更新访问时间 touch -m --更新修改时间(ls -l只能显示修改时间) touch -t [[cc]yy]mmddhhmm[ss] ...
- 【iOS】Apple Mach-O Linker Error Linker command failed with exit code 1
又遇到了这个问题,貌似之前遇到过……如图所示: 解决方法寻找中………… 在 Stack Overflow 找到了解决方法,如下: 参考链接:Apple Mach-O Linker Error
- Android Studio 蓝牙开发实例——基于Android 6.0
因项目需要做一个Android 的蓝牙app来通过手机蓝牙传输数据以及控制飞行器,在此,我对这段时间里写的蓝牙app的代码进行知识梳理和出现错误的总结. 该应用的Compile Sdk Version ...
- 警惕!CAF效应导致PCB漏电
最近碰到一个PCB漏电的问题,起因是一款低功耗产品,本来整机uA级别的电流,常温老化使用了一段时间后发现其功耗上升,个别样机功耗甚至达到了mA级别.仔细排除了元器件问题,最终发现了一个5V电压点,在产 ...
- HTML5 第二章 列表和表格和媒体元素
列表: (1)什么是列表? 列表就是信息资源的一种展示形式. (2)无序列表: 语法: <ul> <li>第1项</li> <li>第2项</li ...
- 运营商手机视频流量包业务日志ETL及统计分析
自己做过的项目在这里做一个记录,否则就感觉不是自己的了.一是因为过去时间已经很长了,二是因为当时做得有点粗糙,最后还不了了之了. 话不多说,先大致介绍一下项目背景.以前各大手机视频 App 一般都有运 ...