概念
响应式流(Reactive Streams)是以带非阻塞背压方式处理异步数据流的标准,提供一组最小化的接口,方法和协议来描述必要的操作和实体。
要解决的问题:
系统之间高并发的大量数据流交互通常采用异步的发布-订阅模式。数据由发布者推送给订阅者的过程中,容易产生的一个问题是,当发布者即生产者产生的数据速度远远大于订阅者即消费者的消费速度时,消费者会承受巨大的资源压力(pressure)而有可能崩溃。
解决原理:
为了解决以上问题,数据流的速度需要被控制,即流量控制(flow control),以防止快速的数据流不会压垮目标。因此需要反压即背压(back pressure),生产者和消费者之间需要通过实现一种背压机制来互操作。实现这种背压机制要求是异步非阻塞的,如果是同步阻塞的,消费者在处理数据时生产者必须等待,会产生性能问题。
解决方法:
响应式流(Reactive Streams)通过定义一组实体,接口和互操作方法,给出了实现非阻塞背压的标准。第三方遵循这个标准来实现具体的解决方案,常见的有Reactor,RxJava,Akka Streams,Ratpack等。
该标准定义了四个接口:
发布者Publisher
interface Publisher《T》 {
void subscribe(Subscriber《? super T》 subscriber);
}
发布者只有一个方法,用来接受订阅者进行订阅(subscribe)。T代表发布者和订阅者之间传输的数据类型。
订阅者Subscriber
interface Subscriber《T》 {
void onSubscribe(Subscription s);
void onNext(T t);
void onError(Throwable t);
void onComplete();
}
订阅者有四个事件方法,分别在开启订阅、接收数据、发生错误和数据传输结束时被调用。
订阅对象Subscription
interface Subscription {
void request(long n);
void cancel();
}
订阅对象是发布者和订阅者之间交互的操作对象,在发布者(Publisher)通过subscribe方法加入订阅者时,会通过调用订阅者(Subscriber)的onSubscribe把订阅对象(Subscription)传给订阅者。
订阅者拿到订阅对象后,通过调用订阅对象的request方法,根据自身消费能力请求n条数据,或者调用cancel方法来停止接收数据。
订阅对象的request方法被调用时,会触发订阅者的onNext事件方法,把数据传输给订阅者。如果数据全部传输完成,则触发订阅者的onComplete事件方法。如果数据传输发生错误,则触发订阅者的onError事件方法。
处理者Processor
interface Processor《T,R》 extends Subscriber《T》, Publisher《R》 {
}
处理者既是发布者又是订阅者,用于在发布者和订阅者之间转换数据格式,把发布者的T类型数据转换为订阅者接受的R类型数据。处理者作为数据转换的中介不是必须的。
由以上的接口可以看出,核心在于订阅者可以通过request(long n)方法来控制接收的数据量,达到了实现背压的目的。
Java对响应式流的支持
Java 9开始,增加了java.util.concurrent.Flow API,把响应式流标准的接口集成到了JDK中,并和响应式流标准接口定义完全一致,之前需要通过Maven引用的接口API,Java 9之后可以直接使用了。第三方实现库也会逐步迁移到JDK的提供的API,当然这个迁移过程需要一段时间。
之前的Maven依赖:
<dependency>
<groupId>org.reactivestreams</groupId>
<artifactId>reactive-streams</artifactId>
</dependency>
下面具体介绍一下java响应式流的处理方式:
java9通过java.util.concurrent.Flow 和java.util.concurrent.SubmissionPublisher 类来实现响应式流
Flow 类中定义了四个嵌套的静态接口,用于建立流量控制的组件,发布者在其中生成一个或多个供订阅者使用的数据项:
Publisher:数据项发布者、生产者
Subscriber:数据项订阅者、消费者
Subscription:发布者与订阅者之间的关系纽带,订阅令牌
Processor:数据处理器
发布者(Publisher)以流的方式发布数据项,并注册订阅者,并且实现 Flow.Publisher 接口,该接口声明了一个方法,我们通过调用它来为发布者注册订阅者:
void subscribe(Flow.Subscriber<? super T> subscriber)
调用此方法来向发布者注册订阅者,但是,如果此订阅者已被其他发布者注册或注册失败(策略冲突),这个方法就会调用订阅者的onError() 方法来抛出IllegalStateException 异常,除此之外,订阅者的onSubscribe() 方法会调用一个新的Flow.Subscription ,当空对象传给订阅者时,subscribe() 方法会抛出NullPointerException异常。
订阅者(Subscriber)从订阅的发布者中返回数据项,并且实现Flow.Subscriber<T> ,这个接口声明的方法如下:
void onSubscribe(Flow.Subscription subscription)
void onComplete()
void onError(Throwable throwable)
void onNext(T item)
onSubscribe() 方法用来确认订阅者注册到发布者是否注册成功,它以参数列表的方式接收一个Flow.Subscription类型的参数,而这个参数类型里面声明的方法允许向发布者请求发布新的数据项,或请求发布者不再发布更多的数据项。
onComplete() 方法用在当订阅者没有调用其他方法,而Subscription 发生错误没有终止的情况下。调用这个方法之后,此订阅者就不能调用其他方法。
onError(Throwable throwable) 方法用在当发布者或订阅者遭遇不可恢复的错误的时候, 调用这个方法之后,此订阅者也不能调用其他方法。
onNext() 方法用于声明下一个数据项的订阅,如果在此过程中抛出异常,结果将得不到确认,甚至会导致订阅被取消。
一个订阅令牌(Subscription)为发布者和订阅者定义一种关系, 使得订阅者接收特定的数据项或者在特定时间取消接收请求,订阅令牌实现自Flow.Subscription 接口,该接口声明方法如下:
void request(long n)
void cancel()
request() 方法添加n个数据项到当前未满的订阅请求中。如果n小于或等于0,订阅者的onError() 方法会被调用,并且抛出IllegalArgumentException 异常,此外,如果n大于0,订阅者就会在onNext() 方法的调用下接收到n个数据项,除非中间异常终止。 从Long.MAX_VALUE次到n次中间是无界的调用。
cancel() 用来终止订阅者接收数据项,它有一种尝试机制,也就是说,在调用它之后也有可能收到数据项。
数据处理器(Processor)在不改变发布者与订阅者的情况下基于流做数据处理,可以在发布者与订阅者之间放多个数据处理器,成为一个处理器链,发布者与订阅者不依赖于数据处理,它们是单独的过程。JDK9中不提供具体的数据处理器,必须由开发者来通过实现无方法声明的Processor接口来自行构建
SubmissionPublisher 实现自Flow.Publisher 接口,向当前订阅者异步提交非空的数据项,直到它被关闭。每个当前订阅者以一个相同的顺序接收新提交的数据项,除非数据项丢失或者遇到异常。SubmissionPublisher 允许数据项在丢失或阻塞的时候扮演发布者角色。
SubmissionPublisher 提供了三个构造方法来获取实例。无参的构造器依赖于 ForkJoinPool.commonPool() 方法来提交发布者,以此实现生产者向订阅者提供数据项的异步特性。
import java.util.Arrays;
import java.util.concurrent.Flow.*;
import java.util.concurrent.SubmissionPublisher;
public class FlowDemo
{
public static void main(String[] args)
{
// Create a publisher.
SubmissionPublisher<String> publisher = new SubmissionPublisher<>();
// Create a subscriber and register it with the publisher.
MySubscriber<String> subscriber = new MySubscriber<>();
publisher.subscribe(subscriber);
// Publish several data items and then close the publisher.
System.out.println("Publishing data items...");
String[] items = { "jan", "feb", "mar", "apr", "may", "jun",
"jul", "aug", "sep", "oct", "nov", "dec" };
Arrays.asList(items).stream().forEach(i -> publisher.submit(i));
publisher.close();
try
{
synchronized("A")
{
"A".wait();
}
}
catch (InterruptedException ie)
{
}
}
}
class MySubscriber<T> implements Subscriber<T>
{
private Subscription subscription;
@Override
public void onSubscribe(Subscription subscription)
{
this.subscription = subscription;
subscription.request(1);
}
@Override
public void onNext(T item)
{
System.out.println("Received: " + item);
subscription.request(1);
}
@Override
public void onError(Throwable t)
{
t.printStackTrace();
synchronized("A")
{
"A".notifyAll();
}
}
@Override
public void onComplete()
{
System.out.println("Done");
synchronized("A")
{
"A".notifyAll();
}
}
}