Rxjava2入門教程五:Flowable背壓支持——對Flowable最全面而詳細的講解

背壓(backpressure)

當上下游在不同的線程中,通過Observable發射,處理,響應數據流時,如果上游發射數據的速度快於下游接收處理數據的速度,這樣對於那些沒來得及處理的數據就會造成積壓,這些數據既不會丟失,也不會被垃圾回收機制回收,而是存放在一個異步緩存池中,如果緩存池中的數據一直得不到處理,越積越多,最後就會造成內存溢出,這便是響應式編程中的背壓(backpressure)問題。
例如,運行以下代碼:

   public void demo1() {
        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);
                    }
                });
    }

創建一個可觀察對象Observable在Schedulers.newThread()的線程中不斷髮送數據,而觀察者Observer在Schedulers.newThread()的另一個線程中每隔5秒接收打印一條數據。
運行後,查看內存使用如下:

由於上游通過Observable發射數據的速度大於下游通過Consumer接收處理數據的速度,而且上下游分別運行在不同的線程中,下游對數據的接收處理不會堵塞上游對數據的發射,造成上游數據積壓,內存不斷增加,最後便會導致內存溢出。

Flowable

既然在函數響應式編程中會產生背壓(backpressure)問題,那麼在函數響應式編程中就應該有解決方案。
Rxjava2相對於Rxjava1最大的更新就是把對背壓問題的處理邏輯從Observable中抽取出來產生了新的可觀察對象Flowable。

在Rxjava2中,Flowable可以看做是爲了解決背壓問題,在Observable的基礎上優化後的產物,與Observable不處在同一組觀察者模式下,Observable是ObservableSource/Observer這一組觀察者模式中ObservableSource的典型實現,而Flowable是Publisher與Subscriber這一組觀察者模式中Publisher的典型實現。

所以在使用Flowable的時候,可觀察對象不再是Observable,而是Flowable;觀察者不再是Observer,而是Subscriber。Flowable與Subscriber之間依然通過subscribe()進行關聯。

雖然在Rxjava2中,Flowable是在Observable的基礎上優化後的產物,Observable能解決的問題Flowable也都能解決,但是並不代表Flowable可以完全取代Observable,在使用的過程中,並不能拋棄Observable而只用Flowable。

由於基於Flowable發射的數據流,以及對數據加工處理的各操作符都添加了背壓支持,附加了額外的邏輯,其運行效率要比Observable慢得多。

只有在需要處理背壓問題時,才需要使用Flowable。

由於只有在上下游運行在不同的線程中,且上游發射數據的速度大於下游接收處理數據的速度時,纔會產生背壓問題;
所以,如果能夠確定:
1、上下游運行在同一個線程中,
2、上下游工作在不同的線程中,但是下游處理數據的速度不慢於上游發射數據的速度,
3、上下游工作在不同的線程中,但是數據流中只有一條數據
則不會產生背壓問題,就沒有必要使用Flowable,以免影響性能。

類似於Observable,在使用Flowable時,也可以通過create操作符創建發射數據流,代碼如下:

public void demo2() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        System.out.println("發射----> 1");
                        e.onNext(1);
                        System.out.println("發射----> 2");
                        e.onNext(2);
                        System.out.println("發射----> 3");
                        e.onNext(3);
                        System.out.println("發射----> 完成");
                        e.onComplete();
                    }
                }, BackpressureStrategy.BUFFER) //create方法中多了一個BackpressureStrategy類型的參數
                .subscribeOn(Schedulers.newThread())//爲上下游分別指定各自的線程
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {   //onSubscribe回調的參數不是Disposable而是Subscription
                        s.request(Long.MAX_VALUE);            //注意此處,暫時先這麼設置
                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println("接收----> " + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                    }

                    @Override
                    public void onComplete() {
                        System.out.println("接收----> 完成");
                    }
                });
    }

運行結果如下:

System.out: 發射----> 1
System.out: 發射----> 2
System.out: 發射----> 3
System.out: 發射----> 完成
System.out: 接收----> 1
System.out: 接收----> 2
System.out: 接收----> 3
System.out: 接收----> 完成

發射與處理數據流在形式上與Observable大同小異,發射器中均有onNext,onError,onComplete方法,訂閱器中也均有onSubscribe,onNext,onError,onComplete方法。

但是在細節方面還是有三點不同
一、create方法中多了一個BackpressureStrategy類型的參數。
二、訂閱器Subscriber中,方法onSubscribe回調的參數不是Disposable而是Subscription,多了行代碼:

s.request(Long.MAX_VALUE);

三、Flowable發射數據時,使用特有的發射器FlowableEmitter,不同於Observable的ObservableEmitter

正是這三點不同賦予了Flowable不同於Observable的特性。

BackpressureStrategy背壓策略

在通過create操作符創建Flowable時,多了一個BackpressureStrategy類型的參數,BackpressureStrategy是個枚舉,源碼如下:

package io.reactivex;

/**
 * Represents the options for applying backpressure to a source sequence.
 */
public enum BackpressureStrategy {
    /**
     * OnNext events are written without any buffering or dropping.
     * Downstream has to deal with any overflow.
     * <p>Useful when one applies one of the custom-parameter onBackpressureXXX operators.
     */
    MISSING,
    /**
     * Signals a MissingBackpressureException in case the downstream can't keep up.
     */
    ERROR,
    /**
     * Buffers <em>all</em> onNext values until the downstream consumes it.
     */
    BUFFER,
    /**
     * Drops the most recent onNext value if the downstream can't keep up.
     */
    DROP,
    /**
     * Keeps only the latest onNext value, overwriting any previous value if the
     * downstream can't keep up.
     */
    LATEST
}

當上遊發送數據的速度快於下游接收數據的速度,且運行在不同的線程中時,Flowable通過自身特有的異步緩存池,來緩存沒來得及處理的數據,緩存池的容量上限爲128,在Flowable源碼的開頭即可看到

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

不同於Observable,其異步緩存沒有容量限制,對於沒來得及處理的數據可以一直向裏面添加,數據過多就會產生內存溢出(OOM)。

BackpressureStrategy的作用便是用來設置Flowable通過異步緩存池緩存數據的策略。在源碼FlowableCreate類中,可以看到五個泛型分別對應五個java類

MISSING   ----> MissingEmitter
ERROR     ----> ErrorAsyncEmitter
DROP      ----> DropAsyncEmitter
LATEST    ----> LatestAsyncEmitter
BUFFER    ----> BufferAsyncEmitter

通過代理模式對原始的發射器進行了包裝。

@Override
    public void subscribeActual(Subscriber<? super T> t) {
        BaseEmitter<T> emitter;

        switch (backpressure) {
            case MISSING: {
                emitter = new MissingEmitter<T>(t);
                break;
            }
            case ERROR: {
                emitter = new ErrorAsyncEmitter<T>(t);
                break;
            }
            case DROP: {
                emitter = new DropAsyncEmitter<T>(t);
                break;
            }
            case LATEST: {
                emitter = new LatestAsyncEmitter<T>(t);
                break;
            }
            default: {
                emitter = new BufferAsyncEmitter<T>(t, bufferSize());
                break;
            }
        }

        t.onSubscribe(emitter);
        try {
            source.subscribe(emitter);
        } catch (Throwable ex) {
            Exceptions.throwIfFatal(ex);
            emitter.onError(ex);
        }
    }

ERROR

對應於ErrorAsyncEmitter<T>類,在其源碼

static final class ErrorAsyncEmitter<T> extends NoOverflowBaseAsyncEmitter<T> {
        private static final long serialVersionUID = 338953216916120960L;

        ErrorAsyncEmitter(Subscriber<? super T> actual) {
            super(actual);
        }

        @Override
        void onOverflow() {
            onError(new MissingBackpressureException("create: could not emit value due to lack of requests"));
        }

    }

onOverflow方法中可以看到
在此策略下,如果放入Flowable的異步緩存池中的數據超限了,則會拋出MissingBackpressureException異常。

運行如下代碼:

public void demo3() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        for (int i = 1; i <= 129; i++) {
                            e.onNext(i);
                        }
                        e.onComplete();
                    }
                }, BackpressureStrategy.ERROR)
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(Long.MAX_VALUE);            //注意此處,暫時先這麼設置
                    }

                    @Override
                    public void onNext(Integer integer) {
                        try {
                            Thread.sleep(10000);
                        } catch (InterruptedException ignore) {
                        }
                        System.out.println(integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println("接收----> 完成");
                    }
                });
    }

創建並通過Flowable發射129條數據,Subscriber的onNext方法睡10秒之後再開始接收,運行後會發現控制檯打印如下異常:

W/System.err: io.reactivex.exceptions.MissingBackpressureException: create: could not emit value due to lack of requests
W/System.err:     at io.reactivex.internal.operators.flowable.FlowableCreate$ErrorAsyncEmitter.onOverflow(FlowableCreate.java:411)
W/System.err:     at io.reactivex.internal.operators.flowable.FlowableCreate$NoOverflowBaseAsyncEmitter.onNext(FlowableCreate.java:377)
W/System.err:     at net.fbi.rxjava2.RxJava2Demo$6.subscribe(RxJava2Demo.java:103)
W/System.err:     at io.reactivex.internal.operators.flowable.FlowableCreate.subscribeActual(FlowableCreate.java:72)
W/System.err:     at io.reactivex.Flowable.subscribe(Flowable.java:12218)
W/System.err:     at io.reactivex.internal.operators.flowable.FlowableSubscribeOn$SubscribeOnSubscriber.run(FlowableSubscribeOn.java:82)
W/System.err:     at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:59)
W/System.err:     at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:51)
W/System.err:     at java.util.concurrent.FutureTask.run(FutureTask.java:237)
W/System.err:     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:154)
W/System.err:     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:269)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
W/System.err:     at java.lang.Thread.run(Thread.java:818)

如果將Flowable發射數據的條數改爲128,則不會出現此異常。

DROP

對應於DropAsyncEmitter<T>類,通過DropAsyncEmitter類和它父類NoOverflowBaseAsyncEmitter的源碼

    static final class DropAsyncEmitter<T> extends NoOverflowBaseAsyncEmitter<T> {
        private static final long serialVersionUID = 8360058422307496563L;

        DropAsyncEmitter(Subscriber<? super T> actual) {
            super(actual);
        }

        @Override
        void onOverflow() {
            // nothing to do
        }
    }
abstract static class NoOverflowBaseAsyncEmitter<T> extends BaseEmitter<T> {

        private static final long serialVersionUID = 4127754106204442833L;

        NoOverflowBaseAsyncEmitter(Subscriber<? super T> actual) {
            super(actual);
        }

        @Override
        public final void onNext(T t) {
            if (isCancelled()) {
                return;
            }

            if (t == null) {
                onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."));
                return;
            }

            if (get() != 0) {
                actual.onNext(t);
                BackpressureHelper.produced(this, 1);
            } else {
                onOverflow();
            }
        }

        abstract void onOverflow();
    }

可以看到,DropAsyncEmitter的onOverflow是個空方法,沒有執行任何操作,父類的onNext中,在判斷get() != 0,即緩存池未滿的情況下,纔會讓被代理類調用onNext方法。
所以在此策略下,如果Flowable的異步緩存池滿了,會丟掉上游發送的數據。
運行如下代碼:

public void demo4() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        String threadName = Thread.currentThread().getName();
                        System.out.println(threadName + "開始發射數據" + System.currentTimeMillis());
                        for (int i = 1; i <= 500; i++) {
                            System.out.println(threadName + "發射---->" + i);
                            e.onNext(i);
                            try {
                                Thread.sleep(100);//每隔100毫秒發射一次數據
                            } catch (Exception ex) {
                                e.onError(ex);
                            }
                        }
                        System.out.println(threadName + "發射數據結束" + System.currentTimeMillis());
                        e.onComplete();
                    }
                }, BackpressureStrategy.DROP)
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(Long.MAX_VALUE);            //注意此處,暫時先這麼設置
                    }

                    @Override
                    public void onNext(Integer integer) {
                        try {
                            Thread.sleep(300);//每隔300毫秒接收一次數據
                        } catch (InterruptedException ignore) {
                        }
                        System.out.println(Thread.currentThread().getName() + "接收---------->" + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println(Thread.currentThread().getName() + "接收----> 完成");
                    }
                });
    }

通過創建Flowable發射500條數據,每隔100毫秒發射一次,並記錄開始發射和結束髮射的時間,下游每隔300毫秒接收一次數據,運行後,控制檯打印日誌如下:

GIF111.gif

通過日誌

1.jpg

 

我們可以發現Subscriber在接收完第128條數據後,再次接收的時候已經到了288,而這之間的160條數據正是因爲緩存池滿了而被丟棄掉了。
那麼問題來了,當Flowable在發射第129條數據的時候,Subscriber已經接收了42條數據了,第129條數據爲什麼沒有放入緩存池中呢?日誌如下:

 

2.jpg

那是因爲緩存池中數據的清理,並不是Subscriber接收一條,便清理一條,而是存在一個延遲,等累積一段時間後統一清理一次。也就是Subscriber接收到第96條數據時,緩存池纔開始清理數據,之後Flowable發射的數據才得以放入。

3.jpg

查看日誌可以發現,Subscriber接收到第96條數據後,Flowable發射第288條數據。而第128到288之間的數據,正好處於緩存池存滿的狀態,而被丟棄,所以Subscriber在接收完第128條數據之後,接收到的是第288條數據,而不是第129條。

LATEST

對應於LatestAsyncEmitter<T>類
與Drop策略一樣,如果緩存池滿了,會丟掉將要放入緩存池中的數據,不同的是,不管緩存池的狀態如何,LATEST都會將最後一條數據強行放入緩存池中,來保證觀察者在接收到完成通知之前,能夠接收到Flowable最新發射的一條數據。
將上述代碼中的DROP策略改爲LATEST:

public void demo5() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        String threadName = Thread.currentThread().getName();
                        System.out.println(threadName + "開始發射數據" + System.currentTimeMillis());
                        for (int i = 1; i <= 500; i++) {
                            System.out.println(threadName + "發射---->" + i);
                            e.onNext(i);
                            try {
                                Thread.sleep(100);
                            } catch (Exception ignore) {
                            }
                        }
                        System.out.println(threadName + "發射數據結束" + System.currentTimeMillis());
                        e.onComplete();

                    }
                }, BackpressureStrategy.LATEST)
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(Long.MAX_VALUE);            //注意此處,暫時先這麼設置
                    }

                    @Override
                    public void onNext(Integer integer) {
                        try {
                            Thread.sleep(300);
                        } catch (InterruptedException ignore) {
                        }
                        System.out.println(Thread.currentThread().getName() + "接收---------->" + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println(Thread.currentThread().getName() + "接收----> 完成");
                    }
                });
    }

運行後日志對比如下:
DROP:

 

DROP.jpg

 

LATEST:

LATEST.jpg

latest策略下Subscriber在接收完成之前,接收的數據是Flowable發射的最後一條數據,而Drop策略下不是。

BUFFER

Flowable處理背壓的默認策略,對應於BufferAsyncEmitter<T>類
其部分源碼爲:

static final class BufferAsyncEmitter<T> extends BaseEmitter<T> {
        private static final long serialVersionUID = 2427151001689639875L;
        final SpscLinkedArrayQueue<T> queue;
        . . . . . .
        final AtomicInteger wip;
        BufferAsyncEmitter(Subscriber<? super T> actual, int capacityHint) {
            super(actual);
            this.queue = new SpscLinkedArrayQueue<T>(capacityHint);
            this.wip = new AtomicInteger();
        }
        . . . . . .
}

在其構造方法中可以發現,其內部維護了一個緩存池SpscLinkedArrayQueue,其大小不限,此策略下,如果Flowable默認的異步緩存池滿了,會通過此緩存池暫存數據,它與Observable的異步緩存池一樣,可以無限制向裏添加數據,不會拋出MissingBackpressureException異常,但會導致OOM。
運行如下代碼:

public void demo6() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        int i = 0;
                        while (true) {
                            i++;
                            e.onNext(i);
                        }
                    }
                }, BackpressureStrategy.BUFFER)
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Consumer<Integer>() {
                    @Override
                    public void accept(@NonNull Integer integer) throws Exception {
                        Thread.sleep(5000);
                        System.out.println(integer);
                    }
                });
    }

查看內存使用:

GIF222.gif

 

會發現和使用Observable時一樣,都會導致內存劇增,最後導致OOM,不同的是使用Flowable內存增長的速度要慢得多,那是因爲基於Flowable發射的數據流,以及對數據加工處理的各操作符都添加了背壓支持,附加了額外的邏輯,其運行效率要比Observable低得多。

MISSING

對應於MissingEmitter<T>類,
通過其源碼:

static final class MissingEmitter<T> extends BaseEmitter<T> {


        private static final long serialVersionUID = 3776720187248809713L;

        MissingEmitter(Subscriber<? super T> actual) {
            super(actual);
        }

        @Override
        public void onNext(T t) {
            if (isCancelled()) {
                return;
            }

            if (t != null) {
                actual.onNext(t);
            } else {
                onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."));
                return;
            }

            for (;;) {
                long r = get();
                if (r == 0L || compareAndSet(r, r - 1)) {
                    return;
                }
            }
        }

    }

可以發現,在傳遞數據時

actual.onNext(t);

並沒有對緩存池的狀態進行判斷,所以在此策略下,通過Create方法創建的Flowable相當於沒有指定背壓策略,不會對通過onNext發射的數據做緩存或丟棄處理,需要下游通過背壓操作符(onBackpressureBuffer()/onBackpressureDrop()/onBackpressureLatest())指定背壓策略。

onBackpressureXXX背壓操作符

Flowable除了通過create創建的時候指定背壓策略,也可以在通過其它創建操作符just,fromArray等創建後通過背壓操作符指定背壓策略。
onBackpressureBuffer()對應BackpressureStrategy.BUFFER
onBackpressureDrop()對應BackpressureStrategy.DROP
onBackpressureLatest()對應BackpressureStrategy.LATEST
例如代碼

    public void demo7() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        for (int i = 0; i < 500; i++) {
                            e.onNext(i);
                        }
                        e.onComplete();
                    }
                }, BackpressureStrategy.DROP)
                .observeOn(Schedulers.newThread())
                .subscribeOn(Schedulers.newThread())
                .subscribe(new Consumer<Integer>() {
                    @Override
                    public void accept(@NonNull Integer integer) throws Exception {
                        System.out.println(integer);
                    }
                });
    }

等同於,代碼:

    public void demo8() {
        Flowable.range(0, 500)
                .onBackpressureDrop()
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Consumer<Integer>() {
                    @Override
                    public void accept(@NonNull Integer integer) throws Exception {
                        System.out.println(integer);
                    }
                });
    }

Subscription

Subscription與Disposable均是觀察者與可觀察對象建立訂閱狀態後回調回來的參數,如同通過Disposable的dispose()方法可以取消Observer與Oberverable的訂閱關係一樣,通過Subscription的cancel()方法也可以取消Subscriber與Flowable的訂閱關係。
不同的是接口Subscription中多了一個方法request(long n),如上面代碼中的:

 s.request(Long.MAX_VALUE);   

此方法的作用是什麼呢,去掉這個方法會有什麼影響呢?
運行如下代碼:

public void demo9() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        System.out.println("發射----> 1");
                        e.onNext(1);
                        System.out.println("發射----> 2");
                        e.onNext(2);
                        System.out.println("發射----> 3");
                        e.onNext(3);
                        System.out.println("發射----> 完成");
                        e.onComplete();
                    }
                }, BackpressureStrategy.BUFFER) //create方法中多了一個BackpressureStrategy類型的參數
                .subscribeOn(Schedulers.newThread())//爲上下游分別指定各自的線程
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        //去掉代碼s.request(Long.MAX_VALUE);
                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println("接收----> " + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                    }

                    @Override
                    public void onComplete() {
                        System.out.println("接收----> 完成");
                    }
                });
    }

運行結果如下:

System.out: 發射----> 1
System.out: 發射----> 2
System.out: 發射----> 3
System.out: 發射----> 完成

我們發現Flowable照常發送數據,而Subsriber不再接收數據。
這是因爲Flowable在設計的時候,採用了一種新的思路——響應式拉取方式,來設置下游對數據的請求數量,上游可以根據下游的需求量,按需發送數據。
如果不顯示調用request則默認下游的需求量爲零,所以運行上面的代碼後,上游Flowable發射的數據不會交給下游Subscriber處理。
運行如下代碼:

public void demo10() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        System.out.println("發射----> 1");
                        e.onNext(1);
                        System.out.println("發射----> 2");
                        e.onNext(2);
                        System.out.println("發射----> 3");
                        e.onNext(3);
                        System.out.println("發射----> 完成");
                        e.onComplete();
                    }
                }, BackpressureStrategy.BUFFER) //create方法中多了一個BackpressureStrategy類型的參數
                .subscribeOn(Schedulers.newThread())//爲上下游分別指定各自的線程
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(2);//設置Subscriber的消費能力爲2
                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println("接收----> " + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                    }

                    @Override
                    public void onComplete() {
                        System.out.println("接收----> 完成");
                    }
                });
    }

運行結果如下:

System.out: 發射----> 1
System.out: 發射----> 2
System.out: 發射----> 3
System.out: 發射----> 完成
System.out: 接收----> 1
System.out: 接收----> 2

我們發現通過s.request(2);設置Subscriber的數據請求量爲2條,超出其請求範圍之外的數據則沒有接收。
多次調用request會產生怎樣的結果呢?
運行如下代碼:

public void demo11() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        String threadName = Thread.currentThread().getName();
                        for (int i = 1; i <= 10; i++) {
                            System.out.println(threadName + "發射---->" + i);
                            e.onNext(i);
                        }
                        System.out.println(threadName + "發射數據結束" + System.currentTimeMillis());
                        e.onComplete();
                    }
                }, BackpressureStrategy.BUFFER)
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(3);//調用兩次request
                        s.request(4);
                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println(Thread.currentThread().getName() + "接收---------->" + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println(Thread.currentThread().getName() + "接收----> 完成");
                    }
                });
    }

通過Flowable發射10條數據,在onSubscribe(Subscription s) 方法中調用兩次request,運行結果如下:

AB417C9CAC5A4BD98375240B5A5C1D6A.jpg

我們發現Subscriber總共接收了7條數據,是兩次需求累加後的數量。

通過日誌我們發現,上游並沒有根據下游的實際需求,發送數據,而是能發送多少,就發送多少,不管下游是否需要。
而且超出下游需求之外的數據,仍然放到了異步緩存池中。這點我們可以通過以下代碼來驗證:

public void demo12() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        for (int i = 1; i < 130; i++) {
                            System.out.println("發射---->" + i);
                            e.onNext(i);
                        }
                        e.onComplete();
                    }
                }, BackpressureStrategy.ERROR)
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(1);
                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println("接收------>" + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println("接收------>完成");
                    }
                });
    }

通過Flowable發射130條數據,通過s.request(1)設置下游的數據請求量爲1條,設置緩存策略爲BackpressureStrategy.ERROR,如果異步緩存池超限,會導致MissingBackpressureException異常。
運行之後,日誌如下:

MissingBackpressureException.jpg

久違的異常出現了,所以超出下游需求之外的數據,仍然放到了異步緩存池中,並導致緩存池溢出。

那麼上游如何才能按照下游的請求數量發送數據呢,
雖然通過request可以設置下游的請求數量,但是上游並沒有獲取到這個數量,如何獲取呢?
這便需要用到Flowable與Observable的第三點區別,Flowable特有的發射器FlowableEmitter

FlowableEmitter

flowable的發射器FlowableEmitter與observable的發射器ObservableEmitter均繼承自Emitter(Emitter在教程二中已經說過了)
比較兩者源碼可以發現;

public interface ObservableEmitter<T> extends Emitter<T> {

    void setDisposable(Disposable d);

    void setCancellable(Cancellable c);

    boolean isDisposed();
  
    ObservableEmitter<T> serialize();
}

public interface FlowableEmitter<T> extends Emitter<T> {

    void setDisposable(Disposable s);

    void setCancellable(Cancellable c);

    long requested();

    boolean isCancelled();

    FlowableEmitter<T> serialize();
}

接口FlowableEmitter中多了一個方法

long requested();

我們可以通過這個方法來獲取當前未完成的請求數量,
運行下面的代碼,這次我們要先喪失一下原則,雖然我們之前說過同步狀態下不使用Flowable,但是這次我們需要先看一下同步狀態下情況。

public void demo13() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        String threadName = Thread.currentThread().getName();
                        for (int i = 1; i <= 5; i++) {
                            System.out.println("當前未完成的請求數量-->" + e.requested());
                            System.out.println(threadName + "發射---->" + i);
                            e.onNext(i);
                        }
                        System.out.println(threadName + "發射數據結束" + System.currentTimeMillis());
                        e.onComplete();
                    }
                }, BackpressureStrategy.BUFFER)//上下游運行在同一線程中
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(3);
                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println(Thread.currentThread().getName() + "接收---------->" + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println(Thread.currentThread().getName() + "接收----> 完成");
                    }
                });
    }

打印日誌如下:

4.jpg

通過日誌我們發現, 通過e.requested()獲取到的是一個動態的值,會隨着下游已經接收的數據的數量而遞減。
在上面的代碼中,我們沒有指定上下游的線程,上下游運行在同一線程中。
這與我們之前提到的,同步狀態下不使用Flowable相違背。那是因爲異步情況下e.requested()的值太複雜,必須通過同步情況過渡一下才能說得明白。
我們在上面代碼的基礎上,給上下游指定獨立的線程,代碼如下

public void demo14() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        String threadName = Thread.currentThread().getName();
                        for (int i = 1; i <= 5; i++) {
                            System.out.println("當前未完成的請求數量-->" + e.requested());
                            System.out.println(threadName + "發射---->" + i);
                            e.onNext(i);
                        }
                        System.out.println(threadName + "發射數據結束" + System.currentTimeMillis());
                        e.onComplete();
                    }
                }, BackpressureStrategy.BUFFER)
                .subscribeOn(Schedulers.newThread())//添加兩行代碼,爲上下游分配獨立的線程
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(3);
                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println(Thread.currentThread().getName() + "接收---------->" + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println(Thread.currentThread().getName() + "接收----> 完成");
                    }
                });
    }

運行後日志如下:

log5.jpg

 

雖然我們指定了下游的數據請求量爲3,但是我們在上游獲取未完成請求數量的時候,並不是3,而是128。難道上游有個最小未完成請求數量?只要下游設置的數據請求量小於128,上游獲取到的都是128?
帶着這個疑問,我們試一下當下遊的數據請求量爲500,大於128時的情況。

public void demo15() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        String threadName = Thread.currentThread().getName();
                        for (int i = 1; i <= 5; i++) {
                            System.out.println("當前未完成的請求數量-->" + e.requested());
                            System.out.println(threadName + "發射---->" + i);
                            e.onNext(i);
                        }
                        System.out.println(threadName + "發射數據結束" + System.currentTimeMillis());
                        e.onComplete();
                    }
                }, BackpressureStrategy.BUFFER)
                .subscribeOn(Schedulers.newThread())//添加兩行代碼,爲上下游分配獨立的線程
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(500);
                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println(Thread.currentThread().getName() + "接收---------->" + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println(Thread.currentThread().getName() + "接收----> 完成");
                    }
                });
    }

運行日誌如下;

 

log6.jpg


結果還是128.
其實不論下游通過s.request();設置多少請求量,我們在上游獲取到的初始未完成請求數量都是128。
這是爲啥呢?
還記得之前我們說過,Flowable有一個異步緩存池,上游發射的數據,先放到異步緩存池中,再由異步緩存池交給下游。所以上游在發射數據時,首先需要考慮的不是下游的數據請求量,而是緩存池中能不能放得下,否則在緩存池滿的情況下依然會導致數據遺失或者背壓異常。如果緩存池可以放得下,那就發送,至於是否超出了下游的數據需求量,可以在緩存池向下遊傳遞數據時,再作判斷,如果未超出,則將緩存池中的數據傳遞給下游,如果超出了,則不傳遞。
如果下游對數據的需求量超過緩存池的大小,而上游能獲取到的最大需求量是128,上游對超出128的需求量是怎麼獲取到的呢?
帶着這個疑問,我們運行一下,下面的代碼,上游發送150個數據,下游也需要150個數據。

 

public void demo16() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        String threadName = Thread.currentThread().getName();
                        for (int i = 1; i <= 150; i++) {
                            System.out.println("當前未完成的請求數量-->" + e.requested());
                            System.out.println(threadName + "發射---->" + i);
                            e.onNext(i);
                        }
                        System.out.println(threadName + "發射數據結束" + System.currentTimeMillis());
                        e.onComplete();
                    }
                }, BackpressureStrategy.BUFFER)
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(150);
                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println(Thread.currentThread().getName() + "接收---------->" + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println(Thread.currentThread().getName() + "接收----> 完成");
                    }
                });
    }

截取部分日誌如下:

log7.jpg


我們發現通過e.requested()獲取到的上游當前未完成請求數量並不是一直遞減的,在遞減到33時,又回升到了128.而回升的時機正好是在下游接收了96條數據之後。我們之前說過,異步緩存池中的數據並不是向下遊發射一條便清理一條,而是每等累積到95條時,清理一次。通過e.requested()獲取到的值,正是在異步緩存池清理數據時,回升的。也就是,異步緩存池每次清理後,有剩餘的空間時,都會導致上游未完成請求數量的回升,這樣既不會引發背壓異常,也不會導致數據遺失。
上游在發送數據的時候並不需要考慮下游需不需要,而只需要考慮異步緩存池中是否放得下,放得下便發,放不下便暫停。所以,通過e.requested()獲取到的值,並不是下游真正的數據請求數量,而是異步緩存池中可放入數據的數量。數據放入緩存池中後,再由緩存池按照下游的數據請求量向下傳遞,待到傳遞完的數據累積到95條之後,將其清除,騰出空間存放新的數據。如果下游處理數據緩慢,則緩存池向下遊傳遞數據的速度也相應變慢,進而沒有傳遞完的數據可清除,也就沒有足夠的空間存放新的數據,上游通過e.requested()獲取的值也就變成了0,如果此時,再發送數據的話,則會根據BackpressureStrategy背壓策略的不同,拋出MissingBackpressureException異常,或者丟掉這條數據。
所以上游只需要在e.requested()等於0時,暫停發射數據,便可解決背壓問題。

 

最終方案

下面我們回到最初的問題
運行下面代碼:

public void demo17() {
        Observable
                .create(new ObservableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(ObservableEmitter<Integer> e) throws Exception {
                        int i = 0;
                        while (true) {
                            i++;
                            System.out.println("發射---->" + i);
                            e.onNext(i);
                        }
                    }
                })
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Observer<Integer>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                    }

                    @Override
                    public void onNext(Integer integer) {
                        try {
                            Thread.sleep(50);
                            System.out.println("接收------>" + integer);
                        } catch (InterruptedException ignore) {
                        }
                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println("接收------>完成");
                    }
                });
    }

由於下游處理數據的速度(Thread.sleep(50))趕不上上游發射數據的速度,則會導致背壓問題。
運行後查看內存使用如下:

GIF333.gif

內存暴增,很快就會OOM
下面,對其通過Flowable做些改進,讓其既不會產生背壓問題,也不會引起異常或者數據丟失。
代碼如下:

public void demo18() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        int i = 0;
                        while (true) {
                            if (e.requested() == 0) continue;//此處添加代碼,讓flowable按需發送數據
                            System.out.println("發射---->" + i);
                            i++;
                            e.onNext(i);
                        }
                    }
                }, BackpressureStrategy.MISSING)
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    private Subscription mSubscription;

                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(1);            //設置初始請求數據量爲1
                        mSubscription = s;
                    }

                    @Override
                    public void onNext(Integer integer) {
                        try {
                            Thread.sleep(50);
                            System.out.println("接收------>" + integer);
                            mSubscription.request(1);//每接收到一條數據增加一條請求量
                        } catch (InterruptedException ignore) {
                        }
                    }

                    @Override
                    public void onError(Throwable t) {
                    }

                    @Override
                    public void onComplete() {
                    }
                });
    }

下游處理數據的速度Thread.sleep(50)趕不上上游發射數據的速度,
不同的是,我們在下游onNext(Integer integer) 方法中,每接收一條數據增加一條請求量,

mSubscription.request(1)

在上游添加代碼

if(e.requested()==0)continue;

讓上游按需發送數據。
運行後查看內存:

GIF999.gif

內存一直相當的平靜,而且上游嚴格按照下游的需求量發送數據,不會產生MissingBackpressureException異常,或者丟失數據。



 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章