引用: https://www.cnblogs.com/IcanFixIt/p/7245377.html
引用: https://juejin.im/post/5e59bc266fb9a07ca90c2ecb
什么是流
流是由生产者生产并由一个或多个消费者消费的元素(item)的序列。
这种生产者-消费者模型也被称为source/sink模型
或发布者-订阅者(publisher-subscriber )模型
。
流的处理机制
有几种流处理机制,其中pull模型和push模型是最常见的。
- 在
push模型
中,发布者将元素推送
给订阅者。 - 在
pull模式
中,订阅者主动拉取发布者生产的元素。
发布者和订阅者都以同样的速率工作,这是一个理想的情况,这些模式非常有效。 但是,如果他们不按同样的速率工作,这种情况下涉及的问题以及对应的解决办法。
当发布者比订阅者快的时候
- 一种解决方案是:后者
必须有一个无边界缓冲区来保存快速传入的元素
,或者它必须丢弃它无法处理的元素
。 - 另一个解决方案是:使用一种称为背压
(backpressure )
的策略。
背压(back pressure)
如果生产者发出的信息比消费者能够处理消息最大量还要多,消费者可能会被迫一直在抓消息,耗费越来越多的资源,埋下潜在的崩溃风险。为了防止这一点,需要有一种机制使消费者可以通知
生产者,降低消息的生成速度。生产者可以采用多种策略来实现这一要求,这种机制称为背压。
简单来说:
- 背压指的发布者和订阅者之间的互动
- 订阅者可以告诉发布者自己需要多少数据,可以调节数据流量,不会导致发布者发布数据过多导致数据浪费或压垮订阅者
响应式流
Reactive Stream 概念
Reactive Stream (响应式流/反应流) 是JDK9引入的一套标准,是一套基于发布/订阅模式的数据处理规范。响应式流从2013年开始,作为提供非阻塞背压的异步流处理标准的倡议。它旨在解决处理元素流的问题——如何将元素流从发布者传递到订阅者,而不需要发布者阻塞
,或订阅者有无限制的缓冲区
或丢弃
。
更确切地说,Reactive流目的是“找到最小的一组接口,方法和协议,用来描述必要的操作和实体以实现这样的目标:以非阻塞背压方式实现数据的异步流”。
反应式流 (Reactive Stream) 规范诞生
Subscription 接口定义了连接发布者和订阅者的方法
Publisher<T> 接口定义了发布者的方法
Subscriber<T> 接口定义了订阅者的方法
Processor<T,R> 接口定义了处理器
Reactive Stream 规范诞生后,RxJava 从 RxJava 2 开始实现 Reactive Stream 规范 , 同时 Spring提供的Reactor 框架(WebFlux的基础) 等也相继实现了 Reactive Stream 规范
订阅者和发布者之间的交互
-
发布者(publisher)
是潜在无限数量的有序元素的生产者。 它根据收到的要求向当前订阅者发布(或发送)元素。 -
订阅者(subscriber)
从发布者那里订阅并接收元素。- 发布者向订阅者发送订阅令牌
(subscription token
)。 - 订阅者使用订阅令牌从发布者哪里请求多个元素。
- 当元素准备就绪时,发布者向订阅者发送多个或更少的元素。
- 订阅者使用订阅令牌来
调整
请求数据的个数
- 发布者向订阅者发送订阅令牌
-
订阅(subscription)
表示订阅者订阅的一个发布者的令牌。 当订阅请求成功时,发布者将其传递给订阅者。 订阅者使用订阅令牌与发布者进行交互,例如请求更多的元素(request)或取消订阅(cancel)。
-处理者(processor)
充当订阅者和发布者的处理阶段。 Processor接口继承了Publisher和Subscriber接口。 它用于转换发布-订阅者管道中的元素。 Processor<T,R>订阅类型T的数据元素,接收并转换为类型R的数据
,并发布变换后的数据。 下图显示了处理者在发布者——订阅和管道中作为转换器的作用。 可以拥有多个处理者。
JDK9 Flow API
JDK9中Reactive Stream的实现规范 通常被称为 Flow API
,通过java.util.concurrent.Flow
来实现响应式流.
APIs
//package java.util.concurrent;
public final class Flow {
@FunctionalInterface
public static interface Publisher<T> {
public void subscribe(Subscriber<? super T> subscriber);
}
public static interface Subscriber<T> {
//发布者subscribe订阅者时触发, 发送subscription
public void onSubscribe(Subscription subscription);
//订阅者接收消息处理逻辑
public void onNext(T item);
public void onError(Throwable throwable);
//发布者 close时触发
public void onComplete();
}
public static interface Subscription {
//订阅者向发布者请求数据
public void request(long n);
public void cancel();
}
//转换器 分别继承了 Subscriber和Publisher接口
public static interface Processor<T,R> extends Subscriber<T>, Publisher<R> {
}
//默认缓冲池大小
static final int DEFAULT_BUFFER_SIZE = 256;
public static int defaultBufferSize() {
return DEFAULT_BUFFER_SIZE;
}
}
demo1
private static void test01() throws InterruptedException {
Random random = new Random();
//1.自定义发布者, 发布数据类型为Integer
//使用jdk9自带的SubmissionPublisher,它实现了Publisher接口
SubmissionPublisher<Integer> publisher = new SubmissionPublisher<>();
//2.定义订阅者
Flow.Subscriber<Integer> subscriber = new Flow.Subscriber<Integer>() {
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription sb) {
//建立订阅关系
this.subscription = sb;
//2. 请求数据 ----
this.subscription.request(1);
}
@Override
public void onNext(Integer item) {
System.out.println("接收到数据:" + item);
this.subscription.request(2);
//已经达到了目标,调用cancel告诉发布者,不在接收数据
// this.subscription.cancel();
}
@Override
public void onError(Throwable throwable) {
//出现异常
throwable.printStackTrace();
this.subscription.cancel();
}
@Override
public void onComplete() {
//在publisher.close();时触发..
System.out.println("数据处理完了");
}
};
// 3.发布者和订阅者 建立订阅关系
publisher.subscribe(subscriber);
//4.生产数据,并发布
publisher.submit(100);
publisher.submit(200);
publisher.submit(300);
publisher.submit(400);
//5.结束后,关闭发布者
publisher.close();
Thread.currentThread().join(1000);
}
demo2–Processor
Proceeor主要的作用是用具加工、处理发布者发布的消息。
下例:用来展示Processor过滤掉发布者生产的小于200的消息,并且对消息进行转换Integer --> String
.
MyProcessor
static class MyProcessor extends SubmissionPublisher<String> implements Flow.Processor<Integer, String> {
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription sb) {
//建立订阅关系
this.subscription = sb;
//2. 请求数据 ----
this.subscription.request(1);
}
@Override
public void onNext(Integer item) {
System.out.println("processor接收到数据:" + item);
//过滤掉小于200的数据,
if(item > 200) {
//将消息转换String
this.submit("String MSG-" + item);
}
this.subscription.request(1);
}
@Override
public void onError(Throwable throwable) {
//出现异常
throwable.printStackTrace();
this.subscription.cancel();
}
@Override
public void onComplete() {
System.out.println("Processor:数据处理完了");
}
}
test-case
private static void testProcessor() throws InterruptedException {
Random random = new Random();
//1.自定义发布者, 发布数据类型为Integer
SubmissionPublisher<Integer> publisher = new SubmissionPublisher<>();
// 2.定义处理器Processor
MyProcessor processor = new MyProcessor();
// 3.发布者和Processor建立关系
publisher.subscribe(processor);
//4.定义订阅者
Flow.Subscriber<String> subscriber = new Flow.Subscriber<String>() {
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription sb) {
//建立订阅关系
this.subscription = sb;
//2. 请求数据 ----
this.subscription.request(1);
}
@Override
public void onNext(String item) {
System.out.println("=============subscriber接收到数据:" + item);
this.subscription.request(2);
//已经达到了目标,调用cancel告诉发布者,不在接收数据
// this.subscription.cancel();
}
@Override
public void onError(Throwable throwable) {
//出现异常
throwable.printStackTrace();
this.subscription.cancel();
}
@Override
public void onComplete() {
//在publisher.close();时触发..
System.out.println("数据处理完了");
}
};
//5. Processor同订阅者建立关系
processor.subscribe(subscriber);
//4.生产数据,并发布
publisher.submit(100);
publisher.submit(200);
publisher.submit(300);
publisher.submit(400);
//5.结束后,关闭发布者
publisher.close();
Thread.currentThread().join(1000);
}
执行结果:
processor接收到数据:100
processor接收到数据:200
processor接收到数据:300
processor接收到数据:400
Processor数据处理完了
=============subscriber接收到数据:String MSG-300
=============subscriber接收到数据:String MSG-400
demo3–背压测试
实现一个场景发布者产生消息的速率 > 订阅者消费消息的速率 && 设置缓冲池大小为2
,
private static void testBackup() throws InterruptedException {
//覆盖默认参数,修改缓冲池最大大小为2
SubmissionPublisher<Integer> publisher = new SubmissionPublisher<>(ForkJoinPool.commonPool(),2){
@Override
public int submit(Integer item) {
System.out.println("生产数据:"+item);
return super.submit(item);
}
};
Flow.Subscriber<Integer> subscriber = new Flow.Subscriber<Integer>() {
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription sb) {
this.subscription = sb;
this.subscription.request(1);
}
@Override
public void onNext(Integer item) {
System.out.println("----------------接收到数据:" + item);
//为了模拟耗时操作: 这里sleep..
try {
TimeUnit.MILLISECONDS.sleep(50L);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.subscription.request(1);
}
@Override
public void onError(Throwable throwable) {
throwable.printStackTrace();
this.subscription.cancel();
}
@Override
public void onComplete() {
System.out.println("数据处理完了");
}
};
// 3.发布者和订阅者 建立订阅关系
publisher.subscribe(subscriber);
//4.生产数据,并发布
Random random = new Random();
IntStream.rangeClosed(1,1000).forEach(data -> {
publisher.submit(data);
});
//5.结束后,关闭发布者
publisher.close();
Thread.sleep(100000L);
}
执行结果
生产数据:1
生产数据:2
----------------接收到数据:1
生产数据:3
生产数据:4
----------------接收到数据:2
生产数据:5
----------------接收到数据:3
生产数据:6
生产数据:7
----------------接收到数据:4
----------------接收到数据:5
可见,只要缓冲池中有超过2
个未处理的数据,消息发布者就会感受到订阅方的压力
,而减缓
产生数据的速率。