吐槽
还有这周5天了,自己暑假留校生活也就结束了,加油还有好多事情没弄玩,再坚持5天
本文重点
- Flowable(背压)的概念和存在的意义
- 基本用法和特点
- 接收数据处理
参考博客https://www.jianshu.com/p/9b1304435564
https://blog.csdn.net/carson_ho/article/details/79081407
之前的问题
之前我们看了异步请求的时候,发现上游和下游的速度不匹配的情况下,内存爆表的时候,当时我们采取的方式是控制数量,控制发送的时间,但都不是完美的解决方式。
因为好比上游和下游是两个人在工作,上游负责发数据,下游负责处理数据,上游不知道下游处理数据的能力,就一次发特别特别多的数据,下游处理不了,就挂了,所以说处理这件事的关键是下游和上游约定处理事务的数量,这样不会因为过多而爆了,不会因为太少而浪费。
所以,就有了背压
背压基本概念
定义:它是一种策略解决了因被观察者发送事件速度 与 观察者接收事件速度 不匹配(一般是前者快于后者从而导致观察者无法及时响应 / 处理所有 被观察者发送事件的问题
作用:控制事件发送 & 接收的速度
范围:必须是异步操作(同步时候操作是一个事件处理完才会处理另一个事件的,所以不用考虑会被挤爆)
背压的基本使用
我们所的上游和下游分别是Observable和Observer, 这次不一样的是上游变成了Flowable, 下游变成了Subscriber, 但是水管之间的连接还是通过subscribe()
步骤1:创建被观察者 = Flowable 需要传入背压参数BackpressureStrategy
步骤2:创建观察者 = Subscriber 对比Observer传入的Disposable参数,Subscriber此处传入的参数 = Subscription
步骤3:建立订阅关系
Flowable<Integer> upstream = Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> e) throws Exception {
Log.d("233","emit 1");
e.onNext(1);
Log.d("233","emit 2");
e.onNext(2);
Log.d("233","emit 3");
e.onNext(3);
Log.d("233","emit comlete");
e.onComplete();
}
}, BackpressureStrategy.ERROR);//增加一个参数
final Subscriber<Integer> downstream = new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d("233","onsubscribe");
s.request(Long.MAX_VALUE);//关键点
}
@Override
public void onNext(Integer integer) {
Log.d("233", "onNext: " + integer);
}
@Override
public void onError(Throwable t) {
Log.w("233", "onError: ", t);
}
@Override
public void onComplete() {
Log.d("233", "onComplete");
}
};
upstream.subscribe(downstream);
注意点:
- 需要传入背压参数BackpressureStrategy,这个参数是用来选择背压,也就是出现上下游流速不均衡的时候应该怎么处理的办法,这里我们直接用BackpressureStrategy.ERROR这种方式, 这种方式会在出现上下游流速不均衡的时候直接抛出一个异常,这个异常就是著名的MissingBackpressureException
- 另外的一个区别是在下游的onSubscribe方法中传给我们的不再是Disposable了, 而是Subscription
- Flowable在设计的时候采用了一种新的思路也就是响应式拉取的方式来更好的解决上下游流速不均衡的问题
什么是响应式拉取
简单的理解就是下游告诉上游,我的处理事情的能力
final Subscriber<Integer> downstream = new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d("233","onsubscribe");
s.request(10);//关键点,我现在能力就是处理10个事件
}
比如,我一次告诉上游我一次能处理10个事件,上游就给我准备10个事件让我处理,然后我又告诉上游我现在能力不行了只能处理5个事件,就上游就发5个事件给下游
所以我们把request当做是一种能力, 当成下游处理事件的能力, 下游能处理几个就告诉上游我要几个, 这样只要上游根据下游的处理能力来决定发送多少事件, 就不会造成一窝蜂的发出一堆事件来, 从而导致内存爆表
所以完美解决了问题
Flowable里面的水缸
之前看的是同步的例子,是在同一个线程里面的处理,现在看下异步处理
上游发1000个,下游一个也不接收
public void demo(){
Flowable<Integer> upstream = Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> e) throws Exception {
// Log.d("233","emit 1");
// e.onNext(1);
// Log.d("233","emit 2");
// e.onNext(2);
// Log.d("233","emit 3");
// e.onNext(3);
// Log.d("233","emit comlete");
// e.onComplete();
for(int i = 0;i < 10000;i++){
Log.d("233","emit "+i);
e.onNext(i);
}
}
}, BackpressureStrategy.DROP)//水缸的大小版本
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());//增加一个参数
final Subscriber<Integer> downstream = new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d("233","onsubscribe");
msubscription = s;
s.request(0);
}
@Override
public void onNext(Integer integer) {
Log.d("233", "onNext: " + integer);
}
@Override
public void onError(Throwable t) {
Log.w("233", "onError: ", t);
}
@Override
public void onComplete() {
Log.d("233", "onComplete");
}
};
upstream.subscribe(downstream);
}
结果就
发送了128个之后就又爆了,128这个数字特别熟悉
原因很简单
上下游没有工作在同一个线程时, 上游却正确的发送了所有的事件,因为在Flowable里默认有一个大小为128的水缸, 当上下游工作在不同的线程中时, 上游就会先把事件发送到这个水缸中, 因此, 下游虽然没有调用request, 但是上游在水缸中保存着这些事件
异步的时候,上游先把数据发到水缸里面,然后下游再从水缸里面取
注意这里我们是把上游发送的事件全部都存进了水缸里, 下游一个也没有消费, 所以就溢出了, 如果下游去消费了事件, 可能就不会导致水缸溢出来了. 这里我们说的是可能不会, 这也很好理解, 比如刚才这个例子上游发了129个事件, 下游只要快速的消费了一个事件, 就不会溢出了, 但如果下游过了十秒钟再来消费一个, 那肯定早就溢出了.
解决水缸爆了的方式也很简单,按照之前的思路,一个对数量上进行控制,一个是对时间上进行控制
- 这里我们就给他个大水缸 将参数BackpressureStrategy.ERROR 变成 BackpressureStrategy.BUFFER
2.运算符处理 rop就是直接把存不下的事件丢弃,Latest就是只保留最新的事件
响应式拉取上游的姿势
Flowable的相应式拉的方式
之前说的是下游可以规定请求多少个,但是上游咋知道下游需要多少个
上游是一次全发完才给下游,还是按照下游的要求给下游的?
但是从我们实际的来看的情况下,上游仍然是一开始就发送了所有的事件
在下游中调用Subscription.request(n)就可以告诉上游,下游能够处理多少个事件,那么上游要根据下游的处理能力正确的去发送事件,那么上游是不是应该知道下游的处理能力是多少啊,沟通肯定是双方面的,下游告诉上游他的能力之后,上游肯定要知道下游的能力
肯定就是FlowableEmitter(发射器这块)
FlowableEmitter是个接口,继承Emitter,Emitter里面就是我们的onNext(),onComplete()和onError()三个方法。我们看到FlowableEmitter中有这么一个方法:long requested();方法注释的意思就是当前外部请求的数量
我们测试下
下游说我能处理10个
public void demo1(){
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> e) throws Exception {
Log.d("233","current requested"+e.requested());
}
},BackpressureStrategy.ERROR)
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d("233","obSubscribe");
msubscription = s;
s.request(10);
}
@Override
public void onNext(Integer integer) {
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
}
});
结果看下上游的表现:
上游显示我知道了,10个
上游的requested的确是根据下游的请求来决定的如果多次进行请求的情况下,就是没有用,但发出去了,做加法
继续进行测试
这次下游先说我能力是10,接着说我能力是100
结果就是,上游说我接收到110
然后发点东西看下
public void demo1(){
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> e) throws Exception {
Log.d("233","current requested "+e.requested());
Log.d("233", "emit 1");
e.onNext(1);
Log.d("233", "after emit 1, requested = " + e.requested());
Log.d("233", "emit 2");
e.onNext(2);
Log.d("233", "after emit 1, requested = " + e.requested());
Log.d("233", "emit 3");
e.onNext(3);
Log.d("233", "after emit 1, requested = " + e.requested());
Log.d("233", "emit complete");
e.onComplete();
Log.d("233", "after emit 1, requested = " + e.requested());
}
},BackpressureStrategy.ERROR)
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d("233","obSubscribe");
msubscription = s;
s.request(10);
// s.request(100); //再给我一百个!
}
@Override
public void onNext(Integer integer) {
Log.d("233", "onNext: " + integer);
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
Log.d("233", "onComplete");
}
});
结果是:
下游调用request(n) 告诉上游它的处理能力,上游每发送一个next事件之后,requested就减一,注意是next事件,complete和error事件不会消耗requested,当减到0时,则代表下游没有处理能力了
在同步的情况下的时候
这张图的意思就是当上下游在同一个线程中的时候,在下游调用request(n)就会直接改变上游中的requested的值,多次调用便会叠加这个值,而上游每发送一个事件之后便会去减少这个值,当这个值减少至0的时候,继续发送事件便会抛异常了。
但是在异步的情况下就又不同了
看下例子
public void demo2(){
Flowable.create(new FlowableOnSubscribe<Integer>() {
@Override
public void subscribe(FlowableEmitter<Integer> e) throws Exception {
Log.d("233","current requested "+e.requested());
// Log.d("233", "emit 1");
// e.onNext(1);
// Log.d("233", "after emit 1, requested = " + e.requested());
//
// Log.d("233", "emit 2");
// e.onNext(2);
// Log.d("233", "after emit 1, requested = " + e.requested());
//
// Log.d("233", "emit 3");
// e.onNext(3);
// Log.d("233", "after emit 1, requested = " + e.requested());
//
// Log.d("233", "emit complete");
// e.onComplete();
// Log.d("233", "after emit 1, requested = " + e.requested());
}
},BackpressureStrategy.ERROR)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
Log.d("233","obSubscribe");
msubscription = s;
s.request(10);
// s.request(100); //再给我一百个!
}
@Override
public void onNext(Integer integer) {
Log.d("233", "onNext: " + integer);
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
Log.d("233", "onComplete");
}
});
}
就只有128,并没有因为下游要的改变
可以看到,当上下游工作在不同的线程里时,每一个线程里都有一个requested,而我们调用request(1000)时,实际上改变的是下游主线程中的requested,而上游中的requested的值是由RxJava内部调用request(n)去设置的,这个调用会在合适的时候自动触发。
我们就能理解为什么没有调用request,上游中的值是128了,因为下游在一开始就在内部调用了request(128)去设置了上游中的值,因此即使下游没有调用request(),上游也能发送128个事件,这也可以解释之前我们为什么说Flowable中默认的水缸大小是128,其实就是这里设置的。
总结
觉得背压就是个策略,一种思想,解决问题的方式,沟通才是解决问题的最好的方法,代码如此,人也如此