Reactor的parallel不支持工作竊取

使用Rx系列框架也有2年多了, 真的感覺它在項目就像是萬金油, 哈哈.

項目裏經常有併發請求外部系統(然後合併結果, 這個今天先不考慮)的情況.

這裏給出一個場景: 需要調用N次外部系統, 調用無返回值, 只需要確保調用結束即可, 可以不處理異常情況, 但需要確保全部調用都完成程序才能繼續執行下去.

如果自己去寫代碼實現這裏就不說了, 大概就是: 拿到一個線程池, 提交多個任務, 根據情況block所有future等待結果.

我知道Rx裏有2種方式可以實現併發操作: parallel操作符 和 flatMap+subscribeOn組合操作符.

 

Parallel使用起來比較簡單, 假設併發度是2, 它將N個請求輪流分到2個隊列裏, 然後每個隊列用一個線程去消費. 這樣有一個缺點就是不支持工作竊取, 比如剛好1個隊列積壓, 另一各隊列已經空閒, 也不會將積壓隊列的工作量分一些到另一個隊列的.

 

使用 flatMap+subscribeOn 的話就沒有這個問題了, 可以用concurrency來控制flatMap的併發度. subscribeOn是爲了讓內部的Mono的阻塞任務跑在IO線程上, 否則內部的Mono會阻塞在subscribe階段, 因爲我們在fromCallable做了阻塞操作sleep.

/**
 * parallel不支持工作竊取, 雖然代碼短一些, 看起來似乎沒flatMap好. 但parallel有其他用處
 *
 * @author xzchaoo
 * @since 2020-02-27
 */
public class ParallelTest {
    /**
     * parallel不支持工作竊取
     */
    @Test
    public void test_parallel_notSupportStealing() {
        Flux.just(9, 1, 1, 1)
            .parallel(2)
            .runOn(Schedulers.elastic())
            .doOnNext(e -> {
                try {
                    System.out.println("在線程 " + Thread.currentThread() + " 收到參數 " + e);
                    TimeUnit.SECONDS.sleep(e);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
            })
            .then()
            .block();
    }

    /**
     * 使用如下的flatMap可以支持工作竊取
     */
    @Test
    public void test_flatMap_supportStealing() {
        Flux.just(9, 1, 1, 1)
            .flatMap(e -> {
                return Mono.fromCallable(() -> {
                    System.out.println("在線程 " + Thread.currentThread() + " 收到參數 " + e);
                    TimeUnit.SECONDS.sleep(e);
                    return null;
                })
                    .subscribeOn(Schedulers.elastic());
            }, 2)
            .ignoreElements()
            .block();
    }
}

 

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