Rxjava系列(七) RxJava2.0背压原理解析

RxJava2.0有一个很大的特色是背压的支持,如果要使用背压的话需要使用 Flowable。为什么需要背压这种机制呢, 先抛开Flowable不说,我们想一个实际应用中的真实案例:如果发送事件和接收事件处于不同的线程中,而且事件处理的速度慢,事件发送的速度快,那么肯定需要一个池子来存储发送的事件等待下游消化,否则消息就会丢失。如果发送事件速度快而接收事件速度慢,那么这个池子会越来越大最终爆掉内存。背压策略正是为了解决这一问题而引出来的。

我们先思考一下,在异步的情况下,如何能让上游发送事件的速度和下游处理事件的速度能够保持一致呢。如果下游能够及时告诉上游它的处理速度,让上游的发送事件速度慢下来,是不是可以呢。带着这个问题我们首先来看下RxJava2.0中的背压策略的实现。
首先我们看一下 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 complete");
                emitter.onComplete();
            }
        }, BackpressureStrategy.ERROR).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Integer>() {

                    @Override
                    public void onSubscribe(Subscription s) {
                        Log.d(TAG, "onSubscribe");
                        mSubscription = s;
                        mSubscription.request(1);
                    }

                    @Override
                    public void onNext(Integer integer) {
                        Log.d(TAG, "onNext: " + integer);
                        mSubscription.request(1);
                    }

                    @Override
                    public void onError(Throwable t) {
                        Log.w(TAG, "onError: ", t);
                    }

                    @Override
                    public void onComplete() {
                        Log.d(TAG, "onComplete");
                    }
                });

仔细看一下上述案例中Flowable 的具体使用方法,里面有两个地方和我们之前Rxjava的使用经验不同,一个是创建Flowable 需要传入一个 BackpressureStrategy的参数,另外是在 Subscriber 的onSubscribe方法中调用 subscription.request 方法。这两处是做什么用的呢。回到开头我们提出的问题,为了解决上游发送事件和下游处理事件速度不一致的问题,我们需要一个策略能让下游告诉上游,下游的处理能力是怎样的,这两处不同就是为了解决这个问题。
首先我们解释下 BackpressureStrategy参数的含义,我们这先介绍下概念,详细理解后续会继续介绍
BackpressureStrategy.ERROR:若上游发送事件速度超出下游处理事件能力,且事件缓存池已满,则抛出异常
BackpressureStrategy.BUFFER:若上游发送事件速度超出下游处理能力,则把事件存储起来等待下游处理
BackpressureStrategy.DROP:若上游发送事件速度超出下游处理能力,事件缓存池满了后将之后发送的事件丢弃
BackpressureStrategy.LATEST:若上有发送时间速度超出下游处理能力,则只存储最新的128个事件
备注:128是Flowable中事件缓存池的大小

这里穿插一下,为什么事件缓存池大小为 128,这个我们可以去看下 Flowable中的部分源码:

/** The default buffer size. */
static final int BUFFER_SIZE;
static {
BUFFER_SIZE = Math.max(16, Integer.getInteger("rx2.buffer-size", 128));
}

Flowable 中默认的buffer大小是128,用来存储上游的事件。

那么这个subscripiton.request方法主要用来做什么呢,这个方法是下游用来告知上游处理事件的能力的。Flowable采用了一种响应式拉取的思路用来解决上下游处理速度不统一的问题。简而言之,所谓响应式拉取就是,下游向上游告知自己的处理能力,下游处理完一个事件,然后告诉上游,上游则继续发送事件,继续等待下游的通知。 当调用subscription.request(1)时,就是下游向上游说:我现在能处理一个事件。

我们上面的例子只是先大概介绍下,Flowable在使用时的不同以及背压的一些基本概念。那么在实际的使用中,如何使用Flowable提供的这些机制来使上下游的处理速度统一起来呢,我们继续向下分析。
之前我们提到调用 subscription.request方法之后,下游就通知了上游自己的处理能力,那么上游需要获知下游的处理能力才能决定怎样发送事件。那么上游通过什么样的方式获知这一数据呢。首先来看一下上游用来发送事件的 FlowableEmitter 的部分源码:

public interface FlowableEmitter<T> extends Emitter<T> {
    void setDisposable(Disposable s);
    void setCancellable(Cancellable c);

    /**
     * The current outstanding request amount.
     * <p>This method is thread-safe.
     * @return the current outstanding request amount
     */
    long requested();  // 获取到当前请求事件的数量

    boolean isCancelled();
    FlowableEmitter<T> serialize();
}

FlowableEmitter中有一个方法requested ,注释显示这个方法的作用是获取当前的请求数量。那么这个方法是否为获取当前下游处理能力的接口,我们可以写个小demo来进行验证:

        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
                        Log.d(TAG, "First requested = " + emitter.requested());
                        boolean flag;
                        for (int i = 0; ; i++) {
                            flag = false;
                            while (emitter.requested() == 0) {
                                if (!flag) {
                                    Log.d(TAG, "can't emit value !");
                                    flag = true;
                                }
                            }
                            emitter.onNext(i);
                            Log.d(TAG, "emit " + i + " , requested = " + emitter.requested());
                        }
                    }
                }, BackpressureStrategy.ERROR)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Integer>() {

                    @Override
                    public void onSubscribe(Subscription s) {
                        Log.d(TAG, "onSubscribe");
                        mSubscription = s;
                        mSubscription.request(3);
                    }

                    @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");
                    }
                });

运行之后我们可以看下结果:

D/TAG: onSubscribe                        
D/TAG: requested = 128        
D/TAG: emit event 1, requested = 127        
D/TAG: onNext: 1                          
D/TAG: emit event 2, requested = 126
D/TAG: onNext: 2
D/TAG: emit event 3, requested = 125
D/TAG: onNext: 3   
D/TAG: emit event 4, requested = 124 
D/TAG: emit event 5, requested = 123
......
D/TAG: emit event 128, requested = 0           

通过代码跑出来的结果发现,不管下游通过s.request()设置的请求值是多少,我们在上游获取到的初始的requested请求数量都是128,随着事件的发送这个值会递减。这个原因是什么呢:前面我们分析过,Flowable内部有一个128大小的事件缓存池,从上游产生的事件,会先放到事件缓存中,然后再交由下游处理,如果下游处理不过来就会一直在缓存池中。因此上游在产生事件的时候,需要先考虑缓存池中是否依然有空间,如果缓存池中已经没有空间而继续发送事件的话容易产生异常,这就是为什么requested的值不是下游请求的事件数量的原因,因为即使上游发送的事件数量超出了下游的数据需求量,也可以放在缓存中,如果没有超过下游请求量,则将缓存池中的数据传递给下游。

那emitter.requested()的值在发送完128个数据之后就递减到0了,按照demo中的写法就没法继续发送事件了,如果下游的请求量大于128 的话,岂不是多于128的事件都无法获取到了。事实是这样吗,我们继续通过demo来进行验证,我们将demo中 mSubscription.request(3)改为 mSubscription.request(96)。我们会看到结果如下所示:

D/TAG: onSubscribe                        
D/TAG: requested = 128        
D/TAG: emit event 1, requested = 127        
D/TAG: onNext: 1                          
D/TAG: emit event 2, requested = 126
D/TAG: onNext: 2
......
D/TAG: emit event 95, requested = 33
D/TAG: onNext: 95   
D/TAG: emit event 96, requested = 95 
D/TAG: emit event 97, requested = 94
......
D/TAG: emit event 223, requested = 0   

从结果可以看出,emitter.requested()数量并不是一直递减的,在递减到33时,又回升到了95。也就是说,异步缓存池中的事件并不是下游处理一条便清除掉一条,而是等到下游累计处理了95条之后,集中清理一次缓存池,这时候缓存池中又有了95个空位,可以继续向缓存池中存储事件了。这样的策略设计,我们在使用背压的时候只需要考虑异步缓存池中是否放得下,缓存池中有空间便可继续发送事件,没有空间则等一等,等到下游处理完95条之后再继续发送。Flowable中的背压机制便是这样来运行的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容