原创:临虹路365号(微信公众号ID:codegod365),欢迎分享,转载请保留出处。
简介
为了更好更深入的了解响应式编程reactive streams规范,我们尝试自己手动实现reactive streams的四个接口。通过对接口的实现,我们也能更好的去理解reactive streams的核心概念,从而为后续学习project reactor打下基础。
Reactive Streams
根据reactive streams的原文介绍,reactive streams为了对异步非阻塞且有背压的流处理提供标准规范,其主要覆盖范围是JVM与Javascript运行时环境与网络协议。
Reactive Streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure. This encompasses efforts aimed at runtime environments (JVM and JavaScript) as well as network protocols.
上面这段简短的描述中,有两个重点:1. 异步非阻塞且有背压的流处理 2. JVM与Javascript运行时环境。
- “异步非阻塞且有背压的流处理”说的就是reactive programming反应式编程,也是反应式宣言里对reactive的定义。
- JVM与Javascript运行时环境,说明支持的力度范围,注意这里的JVM不单单是指java,也包裹所有使用JVM的语言,例如scala、kotlin等。JVM是主要的后端环境,javascript又是主要的前端开发语言,这两者都支持了,也就涵盖了百分之八九十的Web开发了。
Reactive Streams API
反应式编程API主要有三个,Publisher、Subscriber以及Subscription分别对应三个角色,另外一个则是Processor,相当于Publisher和Subscrbier的组合。
- Publisher
public interface Publisher<T> {
public void subscribe(Subscriber<? super T> s);
}
- Subscriber
public interface Subscriber<T> {
void onSubscribe(Subscription s);
void onNext(T t);
void onError(Throwable t);
void onComplete();
}
- Subscription
public interface Subscription {
public void request(long n);
public void cancel();
}
- Processor
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}
一个简易的demo
下面通过以实现上述API接口的方式来展示reactive streams的lifecycle。
在这个demo里,publisher是一个能发[0-size)有限数字的发布者,subscriber则一次性订阅了全部的消息,即请求的大小为Long.MAX_VALUE,也就是纯PUSH模式(关于reactive streams的pull-push模式,请参考背压(Back-Pressure) —— Pull与Push模式的结合)。
为了打印输出结果直观,这里publisher只发送0和1两个数字,即size为2。
public class SimpleOnceRequestDemo {
public static void main(String[] args) {
//0.1 创建消息发布者,以消息类型以Integer为例
System.out.println("Main: 0.1 create a publisher with integer message type");
Publisher<Integer> publisher = new Publisher<Integer>() {
private int start = 0;
//假设这个publisher产生[0-size)的消息
private int size = 2;
private boolean isCancelled = false;
@Override
public void subscribe(Subscriber<? super Integer> subscriber){
//1.1 创建订阅元Subscription
System.out.println("Publisher: 1.1 create a subscription to the Subscriber");
Subscription subscription = new Subscription() {
@Override
public void request(long n) {
//2.1 接受到消息请求以及请求数量n
System.out.println("Publisher: 2.1 receive the request with size from Subscriber");
if (isCancelled) {
//如果已经收到了取消请求,若再收到request请求,则通知错误
subscriber.onError(new IllegalStateException("the subscription is already cancelled"));
}
//开始发送请求数量的消息
long after = start + n;
for (int i = start; i < after; i++) {
if (i == size) {
//2.3 到了数据源的尽头,发送complete消息
System.out.println("Publisher: 2.3 send complete message to the Subscriber");
subscriber.onComplete();
return;
}
//2.2 发送消息给订阅者
System.out.println("Publisher: 2.2 send the message for request size to Subscriber");
subscriber.onNext(i);
}
start = (int)after;
}
@Override
public void cancel() {
//3.1 收到取消的请求,进行取消消息的发送
isCancelled = true;
}
};
//1.2 传递订阅元(Subscription)给Subscriber
System.out.println("Publisher: 1.2 send the subscription to the Subscriber");
subscriber.onSubscribe(subscription);
}
};
//0.2 创建订阅者
System.out.println("Main: 0.2 create a subscriber");
Subscriber<Integer> subscriber = new Subscriber<Integer>(){
private Subscription subscription;
@Override
public void onSubscribe(Subscription s) {
//1.3 收到来自publisher的订阅元subscription
System.out.println("Subscriber: 1.3 receive the subscription from the Publisher");
subscription = s;
//2.0 开始发送消息的请求以及请求数量
System.out.println("Subscriber: 2.0 send the request with size Long.MAX_VALUE");
subscription.request(Long.MAX_VALUE);
}
@Override
public void onNext(Integer msg) {
System.out.println("\tSubscriber: receive the message: " + msg);
}
@Override
public void onError(Throwable t) {
System.out.println("get error: " + t);
}
@Override
public void onComplete() {
System.out.println("\tSubscriber: the publisher is finished to send data");
}
};
//0.3 开始订阅
System.out.println("Main: 0.3 start to subscribe");
publisher.subscribe(subscriber);
}
}
运行结果如下:
Main: 0.1 create a publisher with integer message type
Main: 0.2 create a subscriber
Main: 0.3 start to subscribe
Publisher: 1.1 create a subscription to the Subscriber
Publisher: 1.2 send the subscription to the Subscriber
Subscriber: 1.3 receive the subscription from the Publisher
Subscriber: 2.0 send the request with size Long.MAX_VALUE
Publisher: 2.1 receive the request with size from Subscriber
Publisher: 2.2 send the message for request size to Subscriber
Subscriber: receive the message: 0
Publisher: 2.2 send the message for request size to Subscriber
Subscriber: receive the message: 1
Publisher: 2.3 send complete message to the Subscriber
Subscriber: The publisher is finished to send data
从上面的输出结果我们可以看出,一个reactive streams的生命周期主要有三步: 0. 装配周期 1. 订阅周期 2. 运行周期
为了方便理解,我们假设有三个人,一个是装配工人Main,一个是消息发布者Publisher,一个是消息接收者Subscriber。 因为通常是异步的消息流,所以可以将Main、Publisher、Subscriber理解成三个不同的线程,其中Main就是主线程负责装配。
- 装配周期
在装配周期中,主要由主线程Main来完成业务逻辑的装配工作,将每一个逻辑处理部件组装在一起,即声明式编程的方式,最后通过调用subscribe方法,来触发整个消息流转。
- 订阅周期
订阅周期,即订阅者告知发布者自己要来订阅,使得发布者能知道订阅者的存在,有点类似于客户端Client主动connect到服务端Serverclient.connect(server)。在这里的语法则是server.subscribe(client),看起来像是server主动连接client。其实不是,这样的语法主要是装配工作是由Main完成的,从Main角度是无所谓谁主动连接的谁。因为先有发布者,然后才有订阅者,所以语法publisher.subscribe(subscriber)这样的语法也是很顺畅直观的。
在订阅成功之后,会触发订阅者的onSubscribe方法,其类似于onConnect方法,即表示订阅成功,可以进行消息请求处理了。
- 运行周期
在完成订阅之后,就开始了消息请求和处理的过程,即运行周期。与通常的push模式不同的是,reactive streams采用的是pull-push模式。所以每次要处理消息前,需要先发一个请求消息给publiser,publisher会按请求的数量发消息给订阅者,绝对不会多发。通过这种pull-push的方式从而实现了背压(back-pressure)的功能,提供了对消息源发送消息的管控能力。
虽然说,Main、Publisher、Subscriber对应的是三个角色、三个不同的执行周期,也对应于三个不同的线程,但是在真正实现的时候,往往没有必要独立创建这三类线程,因为创建的线程越多越影响CPU的性能。所以,在没有明确指定线程的时候,Main、Publisher、Subscriber都是默认当前线程,只有当明确指定时,才会选用独立的线程,这里指定Subscriber线程(即订阅周期)的方法是subscribeOn,指定Publisher线程(即运行周期)的方法是publishOn。
另外默认当前线程的顺序是,Pubshlier > Subscriber > Main,即如果没有指定publisher线程,则publisher的线程用的是subscriber的线程。如果Subscriber的线程也没有指定,则subscriber用的是Main线程,此时,如果publisher也没有指定,则publisher用的也是Main线程。
进一步理解 Subscription
Publisher和Subscriber的概念都很好理解,就如同观察者模式中的Observable和Observer,Publisher是消息发送者,Subscriber是消息接收者,Publisher可以直接发消息给Subscriber,那么Subscription是个什么东西呢?
从语义上讲,Subscription是联系Publisher和Subscriber两者之间的桥梁,是由Publisher发布给Subscriber的通信凭证,也是Subscriber用于控制Publisher流量的控制器。(Subscription翻译过来的中文是订阅,订阅在中文里更像是一个动词,为了方便理解,我把Subscription叫做订阅元,类似于token,通信用的令牌)
那么为什么需要在Publisher和Subscriber之间加一个Subscription呢?主要是考虑到Publisher与Subscriber是一个一对多的关系。如果没有Subscription,就很难记录每一个Subscriber订阅到了哪个消息位置了,Subscription就是起这个作用,是Publisher用来表示与记录每一个Subscriber对应状态的工具。
为了更形象的理解,我们用网络通信来举例,这里Publisher相当于Server,Subscriber则相当于Client。那么,Subscription就相当于是channel或者connection,充当了Server与Client之间的连接对象。
- Server可以通过该channel或connection来发送数据给client,即Publisher通过Subscription拿到Subscriber,并调用Subscriber#onNext方法。
- Client可以通过该channel或者connection来发送控制信息给Server,即Subscriber通过Subscription关联到Publisher,并调用Subscription#request或者cancel方法来进行流量控制。
虽然Reactive Streams关注的是程序本身或者说是单机内部,它是一种编程方式,不是种架构工具,但其概念更接近于网络通信或者消息队列,所以我们在使用响应式编程时,可以无限的把Publisher和Subscriber当做是远程通信的Server与Client去理解,这样会更有助于我们形成响应式编程的思维,因为其本质就是异步的非阻塞的,与nio网络通信的方式特别接近。甚至可以说,响应式编程是一种更贴近于物理运行方式的一种编程思维。
总结
本文主要对Reactive Streams涉及到的三个主要API的概念进行了介绍,通过一个手动实现接口的demo来更深入的辅助Reactive Streams思想的理解。尤其重点对Subscription作用进行了细致的介绍。
原创不易,需要一点正反馈,点赞+收藏+关注,三连走一波~ ❤
如果这篇文章对您有所帮助,或者有所启发的话,请关注公众号【临虹路365号】(微信公众号ID:codegod365),您的支持是我们坚持写作最大的动力。