Rxjava的背压策略
同步订阅和异步订阅
1. 同步订阅
- 定义:被观察者与观察者处于同一个线程里面。
- 被观察者发送事件的特点:被观察者每发送一个事件,必须等到观察者接收并处理后,才能继续发送下一个事件。
2. 异步订阅
- 定义:被观察者和观察者处于不同的线程中。
- 被观察者发送事件的特点:被观察者不用等到观察者接收或者处理事件,而是不断发送,直到事件发送完毕。但此时的事件并不会直接发送给观察者,而是存在于缓存区,等待观察者从中取出事件。
异步订阅存在的问题(为什么引入背压策略)
在异步订阅时(比如网络请求),如果被观察者发送事件的速度太快,观察者来不及接收所有的事件,从而缓存区中的事件越积越多,最终就会导致缓存区溢出,时间丢失并OOM(Out Of Memory)。
比如:
Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> e) throws Exception {
int i = 0;
while (true) {
i++;
e.onNext(i);
}
}
}).subscribeOn(Schedulers.newThread()).observeOn(Schedulers.newThread()).subscribe(new Consumer<Integer>() {
@Override
public void accept(Integer integer) throws Exception {
Thread.sleep(5000);
System.out.println(integer);
}
});
背压策略的概述
1. 背压的定义
- Backpressure,也称为Reactive Pull,就是下游需要多少(具体是通过下游的request请求指定需要多少),上游就发送多少。
2. 背压的作用
- 在异步场景中,被观察者发送事件的速度远快于观察者处理的速度的情况下,一种告诉上游的被观察者降低发送速度的策略。
3. 背压策略的原理
- 对于观察者:响应式拉取,即观察者根据自己的实际需求接收事件。
- 对于被观察者:反馈控制,即被观察者根据观察者的接收能力,从而控制事件发送的速度。
- 对于缓存区:对超出缓存区大小的事件进行丢弃,保留,报错。
背压的实现Flowable
1. Flowable的介绍
在Rxjava2.0中,Flowable是被观察者(Observable)的一种新实现,但和Observable不同之处,在于Flowable实现了非阻塞式背压策略。
2. Flowable的特点
- 对应的观察者变成Subscriber
- 所有操作符强制支持背压
- 默认的缓存区大小为:128
- 缓存区使用队列存放事件
3. Flowable与Observable的区别:
- 具体如下图
示意图
- 那么,为什么要采用新实现
Flowable
实现背压,而不采用旧的Observable
呢? - 主要原因:旧实现
Observable
无法很好解决背压问题。
3. Flowable的基本使用
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
Log.d(TAG, "发送事件 1");
emitter.onNext(1);
Log.d(TAG, "发送事件 2");
emitter.onNext(2);
Log.d(TAG, "发送事件 3");
emitter.onNext(3);
Log.d(TAG, "发送完成");
emitter.onComplete();
}
},
BackpressureStrategy.ERROR
).subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
s.request(3);
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
背压的使用
1. 控制观察者接受事件的速度
异步订阅情况
- 简介
- 具体原理图
- 具体使用:
/ 1. 创建被观察者Flowable
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
// 一共发送4个事件
Log.d(TAG, "发送事件 1");
emitter.onNext(1);
Log.d(TAG, "发送事件 2");
emitter.onNext(2);
Log.d(TAG, "发送事件 3");
emitter.onNext(3);
Log.d(TAG, "发送事件 4");
emitter.onNext(4);
Log.d(TAG, "发送完成");
emitter.onComplete();
}
}, BackpressureStrategy.ERROR).subscribeOn(Schedulers.io()) // 设置被观察者在io线程中进行
.observeOn(AndroidSchedulers.mainThread()) // 设置观察者在主线程中进行
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
s.request(3);
// 作用:决定观察者能够接收多少个事件
// 如设置了s.request(3),这就说明观察者能够接收3个事件(多出的事件存放在缓存区)
// 官方默认推荐使用Long.MAX_VALUE,即s.request(Long.MAX_VALUE);
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
- 有两个注意的点:
代码演示:观察者不接收事件的情况下,被观察者继续发送事件 & 存放到缓存区;再按需求取出:
Button button = findViewById(R.id.bt);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mSubscription.request(2);
}
});
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
Log.d(TAG, "发送事件 1");
emitter.onNext(1);
Log.d(TAG, "发送事件 2");
emitter.onNext(2);
Log.d(TAG, "发送事件 3");
emitter.onNext(3);
Log.d(TAG, "发送事件 4");
emitter.onNext(4);
Log.d(TAG, "发送完成");
emitter.onComplete();
}
},
BackpressureStrategy.ERROR
).subscribeOn(Schedulers.io()) // 设置被观察者在io线程中进行
.observeOn(AndroidSchedulers.mainThread()) // 设置观察者在主线程中进行
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
mSubscription = s;
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
代码演示2:观察者不接收事件的情况下,被观察者继续发送事件至超出缓存区大小:
Button button = findViewById(R.id.bt);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mSubscription.request(2);
}
});
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
for (int i = 0;i< 129; i++) {
Log.d(TAG, "发送了事件" + i);
emitter.onNext(i);
}
emitter.onComplete();
}
},
BackpressureStrategy.ERROR
).subscribeOn(Schedulers.io()) // 设置被观察者在io线程中进行
.observeOn(AndroidSchedulers.mainThread()) // 设置观察者在主线程中进行
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
// 默认不设置可接收事件大小
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
同步订阅情况
同步订阅 & 异步订阅 的区别在于:
- 同步订阅中,被观察者 & 观察者工作于同一线程
- 同步订阅关系中没有缓存区
同步订阅与异步订阅的示意图:
- 也就是说,被观察者在发送一个事件之后,必须等到观察者接收后,才能发送下一个事件。换言之就是不会出现发送事件的速度>接收事件的速度,但是会出现发送事件的数目>接收事件的数目这种情况,这种情况依旧会报错。
代码示例:正常操作
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
for (int i = 0; i < 3; i++) {
Log.d(TAG, "发送了事件" + i);
emitter.onNext(i);
}
emitter.onComplete();
}
},
BackpressureStrategy.ERROR
).subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
// 默认不设置可接收事件大小
s.request(3);
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
代码示例:发送事件大于接收事件
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
for (int i = 0; i < 4; i++) {
Log.d(TAG, "发送了事件" + i);
emitter.onNext(i);
}
emitter.onComplete();
}
},
BackpressureStrategy.ERROR
).subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
// 默认不设置可接收事件大小
s.request(3);
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
- 注意:
2. 控制被观察者发送事件的速度
- 简介:
也就是说通过被观察者的FolwableEmitter类的requested()方法来控制发送事件的流速。
FlowableEmitter
类的requested()
介绍
public interface FlowableEmitter<T> extends Emitter<T> {
// FlowableEmitter = 1个接口,继承自Emitter
// Emitter接口方法包括:onNext(),onComplete() & onError
long requested();
// 作用:返回当前线程中request(a)中的a值
// 该request(a)则是措施1中讲解的方法,作用 = 设置
}
原理图:
为了便于理解,我们先列举同步订阅情况的例子:
同步订阅情况:
即在同步订阅情况中,被观察者 通过 FlowableEmitter.requested()获得了观察者自身接收事件能力,从而根据该信息控制事件发送速度,从而达到了观察者反向控制被观察者的效果
具体使用
下面的例子 = 被观察者根据观察者自身接收事件能力(3个事件),从而仅发送3个事件
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
// 调用emitter.requested()获取当前观察者需要接收的事件数量
long n = emitter.requested();
Log.d(TAG, "subscribe: " + n);
for (int i = 0; i < n; i++) {
Log.d(TAG, "发送了事件" + i);
emitter.onNext(i);
}
emitter.onComplete();
}
},
BackpressureStrategy.ERROR
).subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
// 默认不设置可接收事件大小
s.request(3);
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
- 特别注意:在同步订阅情况中使用FlowableEmitter.request()方法时,有以下几个使用特性需要注意:
- 可叠加性:观察者可连续要求接收事件,被观察者会进行叠加并一起发送。
- 实时更新性:每次发送事件后,FlowableEmitter.request()的返回值会实时更新观察者能接收的事件。
- 异常:
- 当FlowableEmitter.request()的返回值=0时,代表观察者已经不可能接收事件。此时若被观察者继续发送事件,则会抛出MissingBackpressureException异常。
- 若观察者没有设置可接收事件数量,则无调用Subscription.request()。那么被关这这默认观察者可接收事件数量=0,即FlowableEmitter.request()的返回值=0;
- 同步订阅关系中不存在缓存区。
异步订阅情况
从最开始的原理可以看出,由于二者处于不同线程,所以被观察者 无法通过 FlowableEmitter.requested()
知道观察者自身接收事件能力,即 被观察者不能根据 观察者自身接收事件的能力 控制发送事件的速度。具体请看下面例子:
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
// 调用emitter.requested()获取当前观察者需要接收的事件数量
Log.d(TAG, "观察者可接收事件数量 = " + emitter.requested());
}
},
BackpressureStrategy.ERROR
).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
// 默认不设置可接收事件大小
s.request(150);
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
从上面可以看出,在异步订阅的关系中,反向控制的原理是:通过Rxjava内部固定调用被观察者线程中的request(n)从而反向控制被观察者的发送事件速度。
- 具体使用
关于RxJava
内部调用request(n)(n = 128、96、0)
的逻辑如下:
至于为什么是调用request(128)
& request(96)
& request(0)
,感兴趣的读者可自己阅读 Flowable
的源码
代码演示:
Button button = findViewById(R.id.bt);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mSubscription.request(48);
}
});
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
// 调用emitter.requested()获取当前观察者需要接收的事件数量
Log.d(TAG, "观察者可接收事件数量 = " + emitter.requested());
boolean flag; //设置标记位控制
// 被观察者一共需要发送500个事件
for (int i = 0; i < 500; i++) {
flag = false;
// 若requested() == 0则不发送
while (emitter.requested() == 0) {
if (!flag) {
Log.d(TAG, "不再发送");
flag = true;
}
}
// requested() ≠ 0 才发送
Log.d(TAG, "发送了事件" + i + ",观察者可接收事件数量 = " + emitter.requested());
emitter.onNext(i);
}
}
},
BackpressureStrategy.ERROR
).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
mSubscription = s;
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
点击两次按钮后:
整个流程 & 测试结果 请看下图:
采用背压模式策略:BackpressureStrategy
背压模式介绍:
在Flowable的使用中,会被要求传入背压模式参数
背压模式类型:
关于背压模式需要解决的问题:
- 流速不匹配,即发送事件速度>接收事件速度
- 当缓存区大小存满,被观察者继续发送下一个事件时
背压策略模式解决方案:
- BackpressureStrategy.ERROR:直接抛出异常MissingBackpressureException
- BackpressureStratery.MISSING:友好提示:缓存区满了
- BackpressureStratery.BUFFER:将缓存区大小设置成无限大,即被观察者可无限发送事件,实际上是放在缓存区。
- BackpressureStratery.DROP:超过缓存区大小(128)的事件丢弃
- BackpressureStratery.LATEST:只保存最新的最后的事件,超过缓存区大小(128)的事件丢弃。
下面我会对每一个模式都逐一说明:
模式1:BackpressureStratery.ERROR
处理方式:直接抛出异常MissingBackpressureException
// 创建被观察者Flowable
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
// 发送 129个事件
for (int i = 0;i< 129; i++) {
Log.d(TAG, "发送了事件" + i);
emitter.onNext(i);
}
emitter.onComplete();
}
}, BackpressureStrategy.ERROR) // 设置背压模式 = BackpressureStrategy.ERROR
.subscribeOn(Schedulers.io()) // 设置被观察者在io线程中进行
.observeOn(AndroidSchedulers.mainThread()) // 设置观察者在主线程中进行
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
模式2:BackpressureStrategy.MISSING:
处理方式:友好提示缓存区满了
模式3:BackpressureStrategy.BUFFER
处理方式:将缓存区大小设置成无限大
模式4: BackpressureStrategy.DROP:
处理方式:超过缓存区大小(128)的事件丢弃,发送了150个事件,仅保存第1 - 第128个事件,第129 -第150事件将被丢弃
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
// 发送150个事件
for (int i = 0;i< 150; i++) {
Log.d(TAG, "发送了事件" + i);
emitter.onNext(i);
}
emitter.onComplete();
}
}, BackpressureStrategy.DROP) // 设置背压模式 = BackpressureStrategy.DROP
.subscribeOn(Schedulers.io()) // 设置被观察者在io线程中进行
.observeOn(AndroidSchedulers.mainThread()) // 设置观察者在主线程中进行
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
mSubscription = s;
// 通过按钮进行接收事件
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mSubscription.request(128);
// 每次接收128个事件
}
});
模式5:BackpressureStrategy.LATEST
处理方式:只保存最新(最后)事件,超过缓存区大小(128)的事件丢弃,如果发送了150个事件,缓存区里会保存129个事件(第1-第128 + 第150事件)。
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
for (int i = 0;i< 150; i++) {
Log.d(TAG, "发送了事件" + i);
emitter.onNext(i);
}
emitter.onComplete();
}
}, BackpressureStrategy.LATEST) // // 设置背压模式 = BackpressureStrategy.LATEST
.subscribeOn(Schedulers.io()) // 设置被观察者在io线程中进行
.observeOn(AndroidSchedulers.mainThread()) // 设置观察者在主线程中进行
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
mSubscription = s;
// 通过按钮进行接收事件
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "接收到了事件" + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mSubscription.request(128);
// 每次接收128个事件
}
});