Rxjava--背壓(Backpressure)

前言:Rxjava是通過觀察者模式設計的異步任務框架,他的有點在於簡潔性,不是代碼的簡潔性,而是邏輯的簡潔性,隨着項目的中異步任務邏輯越來越複雜,Rxjava可以一直保持代碼邏輯的簡潔,不會像handler,ThreadHandler這些在幾次線程間切換以後可能都已經暈頭轉向了.Rxjava提供了多種類的操作符,比如ObserverOn可以爲每次業務的處理的處理制定線程,flatmap這種操作符來幫助我們處理循環遍歷的問題,總的來說rxjava就是時代碼簡潔的異步任務框架.

但是在rxjava1.x版本中存在一個比較深的坑就是,有些操作符支持,背壓,有些操作符不支持背壓,當我們使用不支持背壓的操作符時會報出rx.exceptions.MissingBackpressureException,當我們不理解Backpressure概念時導致我們很難處理這種錯誤。下面我們就來學習rxjava中背壓的概念。

Backpressure
Rx 中的數據流是從一個地方發射到另外一個地方。每個地方處理數據的速度是不一樣的。如果生產者發射數據的速度比消費者處理的快會出現什麼情況?在同步操作中,這不是個問題,例如:

 

Observable<Integer> producer = Observable.create(o -> {
    o.onNext(1);
    o.onNext(2);
    o.onCompleted();
});
// Consume
producer.subscribe(i -> {
    try {
        Thread.sleep(1000);
        System.out.println(i);
    } catch (Exception e) { }
});

雖然上面的消費者處理數據的速度慢,但是由於是同步調用的,所以當 o.onNext(1) 執行後,一直阻塞到消費者處理完才執行 o.onNext(2)。 但是生產者和消費者異步處理的情況很常見。如果是在異步的情況下會出現什麼情況呢?

在傳統的 pull 模型中,當消費者請求數據的時候,如果生產者比較慢,則消費者會阻塞等待。如果生產者比較快,則生產者會等待消費者處理完後再生產新的數據。

而 Rx 爲 push 模型。 在 Rx 中,只要生產者數據好了就發射出去了。如果生產者比較慢,則消費者就會等待新的數據到來。如果生產者快,則就會有很多數據發射給消費者,而不管消費者當前有沒有能力處理數據。這樣會導致一個問題,例如:

 

Observable.interval(1, TimeUnit.MILLISECONDS)
    .observeOn(Schedulers.newThread())
    .subscribe(
        i -> {
            System.out.println(i);
            try {
                Thread.sleep(100);
            } catch (Exception e) { }
        },
        System.out::println);

結果;

 

0
1
rx.exceptions.MissingBackpressureException

上面的 MissingBackpressureException 告訴我們,生產者太快了,我們的操作函數無法處理這種情況。

解決:

RxJava 實現了一種通過 Subscriber 來通知 Observable 發射數據的方式。Subscriber 有個函數 request(n),調用該函數用來通知 Observable 現在 Subscriber 準備接受下面 n 個數據了。在 Subscriber 的 onStart 函數裏面調用 request 函數則就開啓了reactive pull backpressure。這並不是傳統的 pull 模型,並不會阻塞調用。只是 Subscriber 通知 Observable 當前 Subscriber 的處理能力。 通過調用 request 可以發射更多的數據。

pull

觀察者可以根據自身實際情況按需拉取數據,而不是被動接收(也就相當於告訴上游觀察者把速度慢下來),最終實現了上游被觀察者發送事件的速度的控制,實現了背壓的策略。

 

class MySubscriber extends Subscriber<T> {
    @Override
    public void onStart() {
      request(1); //要在onStart中通知被觀察者先發送一個事件
    }

    @Override
    public void onCompleted() {
        ...
    }

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

    @Override
    public void onNext(T n) {
        ...
        request(1); //處理完畢之後,在通知被觀察者發送下一個事件
    }
}


Observable observable=Observable.range(1,100000);
observable.observeOn(Schedulers.newThread())
            .subscribe(new MySubscriber());

在 onStart 函數中調用 request(1) 開啓了 backpressure 模式,告訴 Observable 一次只發射一個數據。在 onNext 裏面處理完該數據後,可以請求下一個數據。通過 quest(Long.MAX_VALUE) 可以取消 backpressure 模式。
實際上,在上面的代碼中,你也可以不需要調用request(n)方法去拉取數據,程序依然能完美運行,這是因爲range –> observeOn,這一段中間過程本身就是響應式拉取數據,observeOn這個操作符內部有一個緩衝區,Android環境下長度是16,它會告訴range最多發送16個事件,充滿緩衝區即可。不過話說回來,在觀察者中使用request(n)這個方法可以使背壓的策略表現得更加直觀,更便於理解。
如果你足夠細心,會發現,在開頭展示異常情況的代碼中,使用的是interval這個操作符,但是在這裏使用了range操作符,爲什麼呢?
這是因爲interval操作符本身並不支持背壓策略,它並不響應request(n),也就是說,它發送事件的速度是不受控制的,而range這類操作符是支持背壓的,它發送事件的速度可以被控制。

Backpressure 策略

很多 Rx 操作函數內部都使用了 backpressure 從而避免過多的數據填滿內部的隊列。這樣處理慢的消費者就會把這種情況傳遞給前面的消費者,前面的消費者開始緩衝數據直到他也緩存滿爲止再告訴他前面的消費者。Backpressure 並沒有消除這種情況。只是讓錯誤延遲發生,我們還是需要處理這種情況。
Rx 中有操作函數可以用來處理這種消費者處理不過來的情況。

onBackpressureBuffer

onBackpressureBuffer 會緩存所有當前無法消費的數據,直到 Observer 可以處理爲止。

 

Observable.interval(1, TimeUnit.MILLISECONDS)
    .onBackpressureBuffer(1000)
    .observeOn(Schedulers.newThread())
    .subscribe(
        i -> {
            System.out.println(i);
            try {
                Thread.sleep(100);
            } catch (Exception e) { }
        },
        System.out::println
    );

上面的示例,生產者比消費者快 100 倍。使用 1000個緩衝來處理這種消費者比較慢的情況。當消費者消費 11個數據的時候,緩衝區滿了,生產者生產了 1100個數據。數據流就拋出異常了。

onBackpressureDrop

如果消費者無法處理數據,則 onBackpressureDrop 就把該數據丟棄了。

 

Observable.interval(1, TimeUnit.MILLISECONDS)
    .onBackpressureDrop()
    .observeOn(Schedulers.newThread())
    .subscribe(
        i -> {
            System.out.println(i);
            try {
                Thread.sleep(100);
            } catch (Exception e) { }
        },
        System.out::println);

結果:

 

0
1
2
...
126
127
12861
12862
...

這個示例中,前面 128 個數據正常的被處理的,這是應爲 observeOn 在切換線程的時候, 使用了一個 128 個數據的小緩衝。

在RxJava1.X中,背壓的設計並不十分完美。而是希望你對背壓有一個全面清晰的認識,對於它在RxJava1.X中的設計缺陷有所瞭解即可。可喜的是,RxJava2.X中解決了背壓的問題,推出了Flowable(Observable在RxJava2.0中新的實現),而且其中的操作符全部都實現了背壓。

發佈了10 篇原創文章 · 獲贊 25 · 訪問量 8124
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章