RxJava3源碼實現-5-流控制

目錄

1、背景

2、sample()-定期採樣「throttleFirst() + throttleLast()」

3、buffer()-批量處理

4、window()-批量處理

5、debounce()-拋棄頻繁變動


 

1、背景

在異步處理的前提下, 我們的Observable.create()發佈數據的數據有可能快於Observer處理的速度。這就導致 Observer來不及接收所有事件,從而導致Observer無法及時響應 / 處理所有發送過來事件的問題,最終導致緩存區溢出、事件丟失 & OOM。

例如:點擊按鈕事件:連續過快的點擊按鈕10次,則只會造成點擊2次的效果;

解釋:因爲點擊速度太快了,所以按鈕來不及響應。

在RxJava實現回壓之前,生產者的生產速度超過消費者的消費速度一直很難解決。爲此,RxJava發明了很多操作符來控制生產者的推送數據的量。從而避免消費者出現處理不了的問題。

2、sample()-定期採樣「throttleFirst() + throttleLast()」

  1. sample()操作符會定期從上游Observable獲取數據,就比如每隔一秒從上面取一次數據,但是這一秒中間的數據將會被丟棄;
  2. throttleLast()和sample()是一樣效果,都會取每個時間段的最後一個值;
  3. throttleFirst()與throttleLast()相反,它會取每個時間段第一個值。

sample()有個重載函數可以傳入一個Observable作爲參數,動態改變監控時間的長短,就比如有些心跳,他們在白天和晚上監控的時間間隔是不一樣的。

注意:如果你用main函數測試下面的代碼的時候,需要加上Thread.sleep(),不然沒有輸出。

    /**
     * 1、interval()-從0開始,每一毫秒+1;
     * 2、sample()每隔1000毫秒從上游取一次值;
     * 預期結果:1000、2000、3000.......
     * 實際結果:1002、2000、3001、4000..... 和預期結果相符,每隔1000毫秒取一次值
     */
    private static void testSample() {

        Observable.interval(1, TimeUnit.MILLISECONDS)
                .sample(1,TimeUnit.SECONDS)
                .take(10)
                .subscribe(System.out::println);
    }
    /**
     * 1、throttleLast()和sample()是一樣效果,都會取每個時間段的最後一個值;
     * 2、就比如:該例子,是從0開始遞增的,但是第一次取的值,是等待一秒以後取的值。
     */
    private static void testThrottleLast() {
        Observable.interval(1, TimeUnit.MILLISECONDS)
                .throttleLast(1,TimeUnit.SECONDS)
                .take(10)
                .subscribe(System.out::println);
    }
    /**
     * 1、throttleFirst()與throttleLast()相反,它會取每個時間段第一個值。
     * 2、所以這裏的結果應該是:0、1000、2000、3000.......
     */
    private static void testThrottleFirst() {
        Observable.interval(1, TimeUnit.MILLISECONDS)
                .throttleFirst(1,TimeUnit.SECONDS)
                .take(10)
                .subscribe(System.out::println);
    }
    public static void main(String[] args) {

        testSample();
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

3、buffer()-批量處理

buffer()操作符會將指定數量的數據聚合成一個List然後發佈給Observer。

實現原理:buffer()會不斷接受上游的事件,並在內部對他們進行緩衝,直到緩衝區的大小(List<Integer> .size() )達到3,此時整個緩衝區(List<Integer>)會被推送下去。如果完成通知出現時,如果內部的緩衝區大小沒達到3,依然會被推送至下游。

適用場景:適用buffer()操作符,可以將細顆粒度的事件替換爲數量更少,但規模更大的批處理。例如:如果你想減少數據庫的負載,那麼就可以將每個事件單獨存儲的方案,替換爲批量存儲。

上面將smaple()函數會每隔一段事件從上游取一個數據;buffer()的一個重載版本,可以將某一個時間段的所有數據打包成List發佈給下游,如下面的例子3。

    /**
     * buffer(3)
     * 輸出結果:
     * [1, 2, 3]
     * [4, 5, 6]
     * [7, 8, 9]
     * [10]
     */
    private static void testbuffer() {
        Observable.range(1, 10)
                .buffer(3)
                .subscribe((List<Integer> list) -> {
                           System.out.println(list);
                });
    }
    /**
     * buffer(1,2)  每次跳過元素的數量
     * 輸出結果:
     *      [1]
     *      [3]
     *      [5]
     *      [7]
     *      [9]
     */
    private static void testbuffer1() {
        Observable.range(1, 10)
                .buffer(1,2)
                .subscribe((List<Integer> list) -> {
                    System.out.println(list);
                });
    }
    /**
     *  buffer(1,TimeUnit.SECONDS)  1秒內緩衝的元素轉換成List。
     *  輸出結果:
     *  [0, 1, 2]
     *  [3, 4, 5]
     *  [6, 7, 8]
     *  [9, 10, 11, 12]
     *  [13, 14, 15]
     */
    private static void testbuffer2() {
        Observable.interval(300, TimeUnit.MILLISECONDS)
                .buffer(1,TimeUnit.SECONDS)
                .take(5)
                .subscribe((List<Long> list) -> {
                    System.out.println(list);
                });
    }

buffer還有一個很厲害的重載版本,它可以控制何時開始緩衝事件,何時停止緩衝事件。

假設現在有這麼個一個業務,服務端不停地給客戶端推送數據,這個數據非常的頻繁。爲了減少客戶端的壓力,客戶端想要每隔一段時間,處理一部分數據就可以了,不需要處理所有的數據。而且在白天業務繁忙階段,加快接受數據的頻率,晚上減少頻率。

  • 9:00-17:00 ,客戶端每隔5秒 截取1秒的數據;
  • 其他的時間,客戶端每隔10秒,截取1秒的數據;

我們的數據是每500毫秒發送一個數據,截取1秒的數據,大約是兩條數據,輸出結果符合我們的預測。

    private static long startTime=System.currentTimeMillis();
    private static void testBuffer3() {

        Observable<Duration> insideBusinessHours=Observable.interval(5,TimeUnit.SECONDS)
                .filter(x->isBusinessHour())
                .map(x->Duration.ofMillis(1000));

        Observable<Duration> outsideBusinessHours=Observable.interval(10,TimeUnit.SECONDS)
                .filter(x->!isBusinessHour())
                .map(x->Duration.ofMillis(1000));

        Observable<Duration> openings=Observable.merge(insideBusinessHours,outsideBusinessHours)
                .doOnNext(s-> System.out.println("時間間隔:"+s.toMillis()+"毫秒"));

        Observable.interval(500, TimeUnit.MILLISECONDS)
                .buffer(openings,duration -> Observable.empty().delay(duration.toMillis(),TimeUnit.MILLISECONDS))
                .subscribe((List<Long> list) -> {
                    long endTime=System.currentTimeMillis();
                    long result=endTime-startTime;

                       System.out.println("time="+(result)+"---"+list);
                       startTime=endTime;
                  });
    }

    /**
     * 判斷是否是  9:00 --17:00 之內的時間段
     */
    private static final LocalTime BUSINESS_START = LocalTime.of(9, 0);
    private static final LocalTime BUSINESS_END = LocalTime.of(17, 0);
    private static boolean isBusinessHour(){
        ZonedDateTime zdt=ZonedDateTime.now();
        LocalTime localTime=zdt.toLocalTime();
        return !localTime.isBefore(BUSINESS_START)&&!localTime.isAfter(BUSINESS_END);
    }

//輸出結果
時間間隔:1000毫秒
time=6229---[10, 11]
時間間隔:1000毫秒
time=4981---[20, 21]
時間間隔:1000毫秒
time=5000---[30, 31]
時間間隔:1000毫秒
time=4997---[40, 41]
時間間隔:1000毫秒
time=5002---[49, 50, 51]
時間間隔:1000毫秒
time=5002---[60, 61]
時間間隔:1000毫秒
time=4996---[69, 70, 71]
時間間隔:1000毫秒
time=5000---[80, 81]
時間間隔:1000毫秒
time=4998---[90, 91]

4、window()-批量處理

window() 和buffer()的功能幾乎是完全一樣,上面buffer()能實現的功能,window()操作符也都可以實現。

buffer()操作符會在緩衝的時候,不斷創建List對象,這在內存消耗方面是難以預料;window()操作符則不會進行中間的緩存,而是採取直接消費的方式。

buffer()的返回結果是:Observable<List<Integer>> 

window()的返回結果是:Observable<Observable<Integer>>

5、debounce()-拋棄頻繁變動

debounce()會對上游數據的發佈頻率進行監聽,比如:debounce(1秒) ,如果兩條數據發佈的時間間隔小於1秒,那麼這兩條數據都不會發布,這時候debounce()會監聽第三個數據,如果第三個數據從發佈到1秒後,都沒有其他數據發佈,那麼這第三條數據就會被髮布。

對於下面第二種沒有輸出的情況,我們可以適用timeout()操作符來報TimeOutException異常來防止無限地靜默狀態。

還有一種解決方案就是利用timeout()提供備用Observable流。

       /**
         * 每個值都會輸出
         */
        Observable.interval(500, TimeUnit.MILLISECONDS)
                .debounce(100,TimeUnit.MILLISECONDS)
                .subscribe(s-> System.out.println("輸出1="+s));
        /**
         * 沒有任何值輸出
         */
        Observable.interval(50, TimeUnit.MILLISECONDS)
                .debounce(100,TimeUnit.MILLISECONDS)
                .subscribe(s-> System.out.println("輸出2="+s));

------------------------解決方案---遞歸------------------------
    timeDebounce(Observable.interval(50, TimeUnit.MILLISECONDS))
                .subscribe(s-> System.out.println("輸出3="+s)) ;

    private static  Observable<Long> timeDebounce(Observable<Long> upStream){
        Observable<Long> onTimeout=upStream
                .take(1)
                .concatWith(Observable.defer(()->timeDebounce(upStream)));
        return upStream.debounce(100,TimeUnit.MILLISECONDS)
                .timeout(1,TimeUnit.SECONDS,onTimeout);
    }
//輸出結果:
輸出3=0
輸出3=0
輸出3=0
輸出3=0
輸出3=0
輸出3=0

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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