一、前言
对于异步订阅关系,存在 被观察者发送事件速度 与观察者接收事件速度 不匹配的情况,被观察者 发送事件速度太快,而观察者 来不及接收所有事件,从而导致观察者无法及时响应 / 处理所有发送过来事件的问题,最终导致缓存区溢出、事件丢失 & OOM,这个时候就需要使用到背压了。
二、详解
2.1、定义
背压是一种控制事件流速的策略,它的作用是在异步订阅关系中,控制事件发送和接收的速度。需要注意的是背压的作用域存在于异步订阅关系中,即 被观察者 和 观察者 处在不同的线程中。它解决了因 被观察者 发送事件速度 与 观察者 接收事件速度 不匹配(一般是前者快于后者),从而导致观察者无法及时响应 / 处理所有 被观察者发送事件 的问题。
它的具体使用场景有很多,比如网络请求时,有很多网络请求需要执行,但执行者的执行速度没那么快,此时就需要使用背压策略来进行控制。
2.2、原理
在 RxJava1.x 中,Observable 是支持背压的,在源码中可以看到 RxJava1.x 中的 Buffer 大小为 16:
public final <B> Observable<List<T>> buffer(Observable<B> boundary) {
return buffer(boundary, 16);
}
我们看下面这个例子:
Observable.create(new Observable.OnSubscribe<Integer>() {
@Override
public void call(Subscriber<? super Integer> subscriber) {
for (int i = 0;; i++) { //无限循环发事件
subscriber.onNext(i);
}
}
}).subscribe(new Action1<Integer>() {
@Override
public void call(Integer integer) {
Log.d(TAG, "" + integer);
}
});
由于缓存池的 buffer 大小为 16,照理说程序运行肯定会抛出那个我们熟悉的异常 MissingBackpressureException,但是结果却是一直在发送和接收事件,导致内存一直增加。其实原理很简单,由于 RxJava 观察者和被观察者处于同一线程中,被观察者需要等待观察者将事件处理完毕后才会继续发送下面的事件,所以才会出现刚才的情况。那么我们添加下面两行代码让它们处于不同的线程再试下:
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
此时果然出现了 MissingBackpressureException。要验证 Buffer 的大小是不是 16 也很容易,在发送事件的 for 循环中将循环值分别设置为 16 和 17 试试就可以了。下面是我对RxJava1.0背压的一些理解:
- 首先,RxJava1.x 的背压事件缓存池很小,只有16,不能够处理较大量的并发事件。
- RxJava1.x 中上游(被观察者)无法得知下游(观察者)对事件的处理能力和事件处理进度,只能把事件一股脑的抛给下游。
- RxJava1.x 有很多事件不能被正确的背压,从而抛出 MissingBackpressureException。
RxJava2.x 中 Observable 不再支持背压,而单独弄了一个 Flowable 来支持背压操作。既然 Observable 不再支持背压,那么我们刚才上面那个例子应该就不会报 MissingBackpressureException 这个异常了吧,我们还是用刚才那个发送无限循环的事件例子(观察者与被观察者在不同线程中)来试试:
在上面的例子里 ,我们在 RxJava2.x 中使用 Observable 一直死循环发送事件,由于下游没有任何背压策略,所以上游的每个事件下游都要一一进行处理,虽然说不报异常了,但是从上图可以看出内存一直在增加,崩溃也是正常现象,这么看来背压操作还是很有必要的。
我们分析阻塞形成的原因,无非是因为下面两点:
- 上游发送事件过快
- 上游发送事件过多
总结来说就是上游短时间发送的事件过多,导致下游忙不过来。这种情况我们可以控制上游发送事件的速度来减少内存消耗,也可以让下游定时取样或者用过滤操作符(filter)过滤一些上游事件来解决阻塞的问题。
三、背压策略
上面给大家阐明了阻塞形成的原因和解决阻塞的方法,基本策略就是减少发送事件的频率和减少发送事件的数量。在 RxJava2.x 中推出了 Flowable 和 Subscriber 用来支持背压,我们来看下 Flowable 的用法:
Flowable.create(FlowableOnSubscribe<T> source, BackpressureStrategy mode)
创建 Flowable 会默认让传入一个 FlowableOnSubscribe 和一个 BackprssureStrategy,FlowableOnSubscribe 很好理解就是 Flowable 的一个被观察者源,而 BackpressureStrategy 就是 Flowable 提供的背压策略。我们来看看有哪些策略:
public enum BackpressureStrategy {
MISSING,
ERROR,
BUFFER,
DROP,
LATEST;
private BackpressureStrategy() {
}
}
如上一共有 5 种:
- MISSING:如果流的速度无法保持同步,可能会抛出 MissingBackpressureException 或 IllegalStateException。
- BUFFER:上游不断的发出 onNext 请求,直到下游处理完,也就是和 Observable 一样了,缓存池无限大,最后直到程序崩溃。
- ERROR:会在下游跟不上速度时抛出 MissingBackpressureException。
- DROP:会在下游跟不上速度时把 onNext 的值丢弃。
- LATEST:会一直保留最新的 onNext 的值,直到被下游消费掉。
先不具体介绍上面的策略,我们先来看看 Flowable 怎么使用:
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
Log.d(TAG, "emit 1");
emitter.onNext(1);
Log.d(TAG, "emit 2");
emitter.onNext(2);
Log.d(TAG, "emit 3");
emitter.onNext(3);
Log.d(TAG, "emit complete");
emitter.onComplete();
}
}, BackpressureStrategy.ERROR)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "onNext: " + integer);
}
@Override
public void onError(Throwable t) {
Log.d(TAG, "onError" + t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
在上述代码中,上游 Flowable 构建 FlowableEmitter 用来发送上游事件,这里的背压策略我们采用 ERROR,下游方法中出现了一个和原来一样的 onSubscribe(Subscription s) 方法,我们来看下 Subscription 的代码:
public interface Subscription {
public void request(long n);
public void cancel();
}
这里需要重点说明一下,在 Flowable 中背压是采取拉取响应的方式来进行水流控制,也就是说 Subscription 是控制上下游水流的主要方式,一般的我们需要调用 Subscription.request() 来传入一个下游目前能处理的事件的数量,如果我们不传的话,上游会发送完所有事件,但是下游却一个事件也接收不到。注意,这里的上下游是在不同的线程里进行的,如果在同一个线程里,它也会抛出一个 MissingBackpressureException,让你去设置调用 request() 方法。
那么也就是说我必须要调用request方法么?官方默认推荐使用 Long.MAX_VALUE,我们来增加以下代码调用一下试试:
@Override
public void onSubscribe(Subscription s) {
s.request(Long.MAX_VALUE); //下游处理事件能力值
}
结果如下:
果然现在的结果正常了,我们再来设置 request(2) 试试,结果如下:
也就是说下游告诉上游我们能处理 2 个事件,这样上游就在缓存池中取出了 2 个事件给发送给了下游。这点相比 Rxjava1.x 可以说是智能了很多,并不会一股脑的抛给下游而是由下游来主动拉取事件。
Flowable 跑通了,现在我们来试试背压策略吧。
3.1、BackpressureStrategy.ERROR
先还是采用 BackpressureStrategy.ERROR 这个策略,如果下游处理不过来就抛出异常,代码如下:
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
for (int i = 0;i< 128; i++) {
Log.d(TAG, "emit " + i);
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");
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "onNext: " + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
我们让上游发送 128 个事件,下游不做处理,结果是正常的。现在我们把128改成129,结果如下:
怎么就抛出异常了呢,我们还是看下源码:
public abstract class Flowable<T> implements Publisher<T> {
/** The default buffer size. */
static final int BUFFER_SIZE;
static {
BUFFER_SIZE = Math.max(1, Integer.getInteger("rx2.buffer-size", 128));
}
}
可以看到,原来 Flowable 缓存池的最大值是 128,如果缓存池里由超过 128 个事件就会抛出异常提示你去处理这些事件。现在我们把缓存策略设置为 BackpressureStrategy.MISSING 来试试:
3.2、BackpressureStrategy.MISSING
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
for (int i = 0;i< 129; i++) {
Log.d(TAG, "emit " + i);
emitter.onNext(i);
}
}
}, BackpressureStrategy.MISSING).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
subscription = s;
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "onNext: " + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
结果还是一样报异常,不过这次很友好的提示你队列满了。
3.3、BackpressureStrategy.BUFFER
BUFFER是一个无限大的缓存池,也就是说我们可以往里面存储无限多的事件,我们来看下代码:
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
for (int i = 0;i<129 ; i++) {
Log.d(TAG, "emit " + i);
emitter.onNext(i);
}
}
}, BackpressureStrategy.BUFFER).subscribeOn(Schedulers.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, "onNext: " + integer);
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
结果正常,但是如果我们要发送无数多的事件同样要注意内存情况。
3.4、BackpressureStrategy.DROP
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个事件
}
});
被观察者一下子发送了 150 个事件,点击按钮接收时观察者接收了 128 个事件;再次点击接收时却无法接受事件,这说明超过缓存区大小的事件被丢弃了。
3.5、BackpressureStrategy.LATEST
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个事件
}
});
被观察者一下子发送了 150 个事件,点击按钮接收时观察者接收了 128 个事件,再次点击接收时却接收到1个事件(第150个事件),这说明超过缓存区大小的事件仅保留最后的事件(第150个事件)。
需要特别注意的是,Flowable 可通过自己创建(如上面例子),或通过其他方式自动创建,如 interval 操作符。对于自身手动创建 FLowable 的情况,可通过传入背压模式参数选择背压策略(即上面描述的)。可是对于自动创建 FLowable,却无法手动传入传入背压模式参数,那么出现流速不匹配的情况下,就需要使用 RxJava2.x 内部提供 封装了背压策略模式的方法:
- onBackpressureBuffer()
- onBackpressureDrop()
- onBackpressureLatest()
默认采用 BackpressureStrategy.ERROR 模式,具体使用方式如下:
Flowable.interval(1, TimeUnit.MILLISECONDS)
.onBackpressureBuffer() // 添加背压策略封装好的方法,此处选择Buffer模式,即缓存区大小无限制
.observeOn(Schedulers.newThread())
.subscribe(new Subscriber<Long>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(TAG, "onSubscribe");
mSubscription = s;
s.request(Long.MAX_VALUE);
}
@Override
public void onNext(Long aLong) {
Log.d(TAG, "onNext: " + aLong);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void onError(Throwable t) {
Log.w(TAG, "onError: ", t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
});
四、总结
虽然在 Rxjava2 中,Flowable 是在 Observable 的基础上优化后的产物,Observable 能解决的问题 Flowable 也都能解决,但是并不代表 Flowable 可以完全取代 Observable,在使用的过程中并不能抛弃Observable而只用Flowable。由于基于 Flowable 发射的数据流,以及对数据加工处理的各操作符都添加了背压支持,附加了额外的逻辑,其运行效率要比 Observable 慢得多。所以只有在需要处理背压问题时,才需要使用Flowable。