从 Java 9 开始提供了 Reactive Streams API ( java.util.concurrent.Flow), 实现了异步非阻塞的流处理方式。有关响应式流介绍 reactive-streams
Reactive Streams 是通过异步处理流的方式,因此他们有一组 Publisher 和 Subscriber,Publisher 将数据流 push 到 Subscriber,Subscriber 则将消费这些数据流,并通过 backpressure(个人理解为回压)来反馈 Subscriber 消费时的压力,调节 Publisher 处理的速度
有时我们必须在 Publisher 和 Subsriber 之间转换数据。 Processor 是位于 End Publisher 和 End Subsriber 之间的实体,用于转换从 Publisher 接收的数据,以便 Subsriber 可以理解它。Processor 可以使用多个组成 Processor Chain 链去处理数据
Java 9 Flow API
Java 9 Flow API 实现了 Reactive Streams 规范。 Flow API 是 Iterator 和 Observer 模式的组合。 Iterator 在 pull 模型上工作,其中应用程序从源中拉出数据,而 Observer 在 push 模型上工作,并在将数据从源推到应用程序时进行处理。
Java 9 Flow API订阅者可以在订阅发布者的同时请求 N 个项目。 然后将项目从 Publisher 推送到 Subsriber,直到没有其他项目可推送或出现一些错误为止。
java.util.concurrent.Flow.Publisher
Publisher 函数式接口用于将数据流发送到 Subscriber
public void subscribe(Subscriber super T> subscriber);
subscribe 方法用于指定订阅者 Subscriber
java.util.concurrent.Flow.Subscriber
Subscriber 则为消费处理 Publisher 发送过来的数据流
public void onSubscribe(Subscription subscription);
public void onNext(T item);
public void onError(Throwable throwable);
public void onComplete();
- onSubscribe: 这是在 Subscriber 服务器订阅 Publisher 以接收消息时调用的第一种方法。 通常,我们调用 subscription.request 开始从处理器接收具体数量的数据流。
- onNext: 从 Publisher 处收到数据时,将调用此方法,这是我们在其中实现业务逻辑以处理 data stream,然后从 Publisher 处请求更多数据的地方。
- onError:当发生不可恢复的错误时,将调用此方法,我们可以使用此方法清理任务,例如关闭数据库连接。
- onComplete: 这类似于 finally 方法,并且在 Publisher 没有 push 任何其他 data stream 并且关闭 Publisher 时被调用。 我们可以使用它发送成功处理流的通知。
java.util.concurrent.Flow.Subscription
这用于在 Publisher 和 Subscriber 之间创建异步非阻塞连接。 Subscriber 调用其请求方法以向 Publisher 获取新的 data stream。 它还具有取消方法以取消订阅,即关闭 Subscriber 和 Publisher 之间的连接。
java.util.concurrent.Flow.Processor
此接口同时扩展了 Publisher 和 Subscriber,用于在 Publisher 和 Subscriber 之间转换消息
java.util.concurrent.SubmissionPublisher
发布者实现异步提交数据流到当前的订阅者中直到连接被关闭,通过使用 Exceutor 框架提交 reactive stream 数据到订阅者
Java Example
- 定义数据流实体 Employee , 用于数据传输
public class Employee {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Employee() {
}
public Employee(int i, String s) {
this.id = i;
this.name = s;
}
@Override
public String toString() {
return "[id=" + id + ",name=" + name + "]";
}
}
- 定义 Subsriber 订阅者 实现 Subscriber
接口,用于对数据进行处理
public class MySubscriber implements Subscriber {
private Subscription subscription;
private int count = 0;
@Override
public void onSubscribe(Subscription subscription) {
System.out.println("Subscribed");
this.subscription = subscription;
this.subscription.request(1);
System.out.println("onSubscribe requested 1 item");
}
@Override
public void onNext(Employee item) {
System.out.println("Process Employee" + count);
count++;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.subscription.request(1);
}
@Override
public void onError(Throwable throwable) {
System.out.println("some error happen" + throwable);
}
@Override
public void onComplete() {
System.out.println("all process done");
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
subscription 变量需要保存其引用,以此在 onNext 方法中对 Publisher 进行请求数据流
count 变量用于记录处理的次数,将在 main thread 中通知任务是否处理完成
- Publisher 示例程序
我们将使用 SubmissionPublisher 作为发布者作为示例,因此让我们看一下响应流实现的测试程序。
public static void main(String[] args) throws InterruptedException {
//create publisher
SubmissionPublisher publisher = new SubmissionPublisher<>();
//register subscriber
MySubscriber subscriber = new MySubscriber();
publisher.subscribe(subscriber);
List employees = xxxxx;
//publish items
employees.forEach(publisher::submit);
while (employees.size() != subscriber.getCount()) {
TimeUnit.SECONDS.sleep(5);
}
publisher.close();
System.out.println("exiting app");
}
Back Pressure
当发布者以比订阅者消耗的速度快得多的速度生成消息时,就会形成回压。 Flow API 没有提供任何机制来发出有关背压的信号或进行处理。 但是我们可以设计自己的策略来处理它,例如微调用户或降低消息产生率。 SubmissionPublisher 中提供了一个 buffer 的机制,允许 Subsriber 最大处理的量,超过该数量将被阻塞