Reactor中 publishOn 與 subscribeOn的區別

publishOn 和 subscribeOn

publishOn 和 subscribeOn。這兩個方法的作用是指定執行 Reactive StreamingScheduler(可理解爲線程池), 不過這兩個方法是用的場景是不相同的.

爲何需要指定執行 Scheduler 呢?一個顯而易見的原因是:組成一個反應式流的代碼有快有慢,可能你的下游接口RT是不同的。如果將這些功能都放在一個線程裏執行,快的就會被慢的影響,所以需要相互隔離。這是這兩個方法應用的最典型的場景。

Scheduler

先介紹 Scheduler 這個概念。在 Reactor 中,Scheduler 用來定義執行調度任務的抽象。可以簡單理解爲線程池,但其實際作用要更多。Scheduler 的實現包括:

  1. Schedulers.elastic(): 調度器會動態創建工作線程,線程數無上界

  2. Execturos.newCachedThreadPool()

  3. 當前線程,通過 Schedulers.immediate()方法來創建。

  4. 單一的可複用的線程,通過 Schedulers.single()方法來創建。

  5. 使用對並行操作優化的線程池,通過 Schedulers.parallel()方法來創建。其中的線程數量取決於 CPU 的核的數量。該調度器適用於計算密集型的流的處理。

  6. 使用支持任務調度的調度器,通過 Schedulers.timer()方法來創建。
    從已有的 ExecutorService 對象中創建調度器,通過 Schedulers.fromExecutorService()方法來創建。

Mono<Void> fluxToBlockingRepository(Flux<User> flux, 
                                    BlockingRepository<User> repository) {
    return flux
            .publishOn(Schedulers.elastic())
            .doOnNext(repository::save)
            .then();
}
Flux<User> blockingRepositoryToFlux(BlockingRepository<User> repository)  {
    return Flux.defer(() -> Flux.fromIterable(repository.findAll()))
               .subscribeOn(Schedulers.elastic());
}

這裏的 repository 的類型是BlockingRepository,通常指的是會導致線程阻塞的IO操作

在第一個例子中,在執行了publishOn(Schedulers.elastic()) 之後,repository::save就會被 Schedulers.elastic()定義的線程池所執行。

而在第二個例子中,subscribeOn(Schedulers.elastic()) 的作用類似。它使得 repository.findAll()(也包括 Flux.fromIterable)的執行發生在 Schedulers.elastic()所定義的線程池中。

從上面的描述看,publishOnsubscribeOn 的作用類似,那兩者的區別又是什麼?

兩者的區別
簡單說,兩者的區別在於影響範圍。publishOn 影響在其之後的 operator執行的線程池,而 subscribeOn 則會從源頭影響整個執行過程。所以,publishOn 的影響範圍和它的位置有關,而 subscribeOn的影響範圍則和位置無關。

看個 publishOn 和 subscribeOn 同時使用的例子

Flux.just("tom".map(s -> {
            System.out.println("[map] Thread name: " + Thread.currentThread().getName());
            return s.concat("@mail.com");
        })
        .publishOn(Schedulers.newElastic("thread-publishOn"))
        .filter(s -> {
            System.out.println("[filter] Thread name: " + Thread.currentThread().getName());
            return s.startsWith("t");
        })
        .subscribeOn(Schedulers.newElastic("thread-subscribeOn"))
        .subscribe(s -> {
            System.out.println("[subscribe] Thread name: " + Thread.currentThread().getName());
            System.out.println(s);
        });

輸出結果如下:

[map] Thread name: thread-subscribeOn-3
[filter] Thread name: thread-publishOn-4
[subscribe] Thread name: thread-publishOn-4
tom@mail.com

從上面的例子可以看出,subscribeOn 定義在publishOn之後,但是卻從源頭開始生效。而在 publishOn執行之後,線程池變更爲 publishOn 所定義的。

實際用途
這裏介紹 publishOnsubscribeOn的一種實際用途,那就是反應式編程和傳統的,會導致線程阻塞的編程技術混用的場景。其實開頭兩個例子已經解釋了這個場景。

在第一個 publishOn 的例子中,repository::save 會導致線程阻塞,爲了避免造成對其它反應式操作的影響,便使用 publishOn 改變其執行線程。

在第二個 subscribeOn 的例子中,repository.findAll() 會導致線程阻塞。但是其是源頭的 publisher,因此不能使用 publishOn 改變其 執行線程。這時就需要使用 subscribeOn,在源頭上修改其執行線程。

這樣,通過 publishOn 和 subscribeOn 就在反應式編程中實現了線程池隔離的目的,一定程度上避免了會導致線程阻塞的程序執行影響到反應式編程的程序執行效率。

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