理解響應式型別

工程 | Sébastien Deleuze | 2016年4月19日 | ...

繼之前的響應式 SpringReactor Core 3.0博文之後,我想解釋一下響應式型別為何有用,以及它們與其它非同步型別相比有何不同,這是基於我們在 Spring Framework 5 即將推出的響應式支援方面所學到的知識。

為什麼要使用響應式型別?

響應式型別並非旨在讓你更快地處理請求或資料,事實上,與常規的阻塞式處理相比,它們會引入一些輕微的開銷。它們的優勢在於能夠更高效地同時處理更多請求,並處理那些具有延遲的操作,例如從遠端伺服器請求資料。它們使你能夠提供更好的服務質量和可預測的容量規劃,因為它們原生處理時間和延遲,而不會消耗更多資源。與等待結果時阻塞當前執行緒的傳統處理方式不同,等待的響應式 API 幾乎不消耗成本,僅請求它能夠處理的資料量,並且由於它處理的是資料流,而不僅僅是單個元素,因此帶來了新的功能。

Java 8 之前

在 Java 8 之前,實現非同步非阻塞行為至少有兩個原因不夠明顯。第一個原因是基於回撥的 API 需要冗餘的匿名類,並且不易於鏈式呼叫。第二個原因是 Future 型別是非同步的,**但是**當你嘗試使用 get() 方法獲取結果時,它會阻塞當前執行緒直到計算完成。這就是為什麼 Spring Framework 4.0 引入了 ListenableFuture,一個增加了非阻塞回調功能的 Future 實現。

Lambda 表示式、CompletableFuture 和 Stream

然後 Java 8 引入了 Lambda 表示式和 CompletableFuture。Lambda 表示式允許編寫簡潔的回撥,而 CompletionStage 介面和 CompletableFuture 類最終允許以非阻塞和基於推的方式處理 Future,同時提供對這種延遲結果處理進行鏈式呼叫的能力。

Java 8 還引入了 Stream,它被設計用於高效地處理可以以幾乎零延遲訪問的資料流(包括原始型別)。它是基於拉取的,只能使用一次,缺少與時間相關的操作,並且可以執行平行計算,但無法指定使用的執行緒池。正如 Brian Goetz 所解釋的,它並沒有被設計用於處理具有延遲的操作,例如 I/O 操作。而這正是 Reactor 或 RxJava 等響應式 API 發揮作用的地方。

響應式 API

Reactor 這樣的響應式 API 也提供了類似 Java 8 Stream 的運算子,但它們更通用地處理任何流序列(不僅僅是集合),並且允許定義一個轉換操作管道,透過方便的鏈式 API 並使用 Lambda 表示式將這些操作應用於經過的資料。它們被設計用於處理同步或非同步操作,並允許你對資料進行緩衝、合併、連線或應用廣泛的轉換。

最初,響應式 API 只設計用於處理資料流,即 N 個元素,例如使用 Reactor 的 Flux

reactiveService.getResults()
    .mergeWith(Flux.interval(100))
    .doOnNext(serviceA::someObserver)
    .map(d -> d * 2)
    .take(3)
    .onErrorResumeWith(errorHandler::fallback)
    .doAfterTerminate(serviceM::incrementTerminate)
    .consume(System.out::println);

但在我們為 Spring Framework 5 所做的工作中,很明顯需要區分單個元素流和多個元素流,這就是為什麼 Reactor 提供了 Mono 型別。MonoCompletableFuture 型別的響應式等價物,並允許提供一個統一的 API,以響應式的方式處理單個和多個元素。

Mono.any(reactiveServiceA.findRecent(time), reactiveServiceB.findRecent(time)
    .timeout(Duration.ofSeconds(3), errorHandler::fallback)
    .doOnSuccess(r -> reactiveServiceC.incrementSuccess())
    .consume(System.out::println);

如果你更深入地研究 FluxMono,你會發現這些型別實現了 Reactive Streams 規範中的 Publisher 介面。

Reactive Streams

Reactor 構建於 Reactive Streams 規範之上。Reactive Streams 由 4 個簡單的 Java 介面PublisherSubscriberSubscriptionProcessor)、一個 文字規範和一個 TCK 組成。它是所有現代響應式庫的基石,並且對於互操作性至關重要。

Reactive Streams 的核心關注點是處理背壓。簡而言之,背壓是一種機制,允許接收者詢問它希望從發射器接收多少資料。它允許:

  • 接收者在準備好處理資料後才開始接收資料
  • 控制在途資料的量
  • 高效處理慢發射器/快接收器或快發射器/慢接收器用例
  • 如果你請求 Long.MAX_VALUE 個元素,則可以從動態的推-拉策略切換到僅基於推的策略

乍一看,Publisher 介面似乎出奇地簡單;但要完全符合規範來實現它卻相當困難,使用者除了訂閱它之外,無法對原始 Publisher 做任何事情!因此,通常最好依賴 Reactive Streams 的實現,例如 Reactor,來提供幫助。

請注意,Java 9 將把 Reactive Streams 介面包含在 java.util.concurrent.Flow 容器類中,這進一步表明了 Reactive Streams 在 JDK 中的重要性。

另外,值得注意的是,向 Reactive Streams 的融合以及 Reactor 的轉換能力允許在執行時輕鬆有效地將一種響應式型別轉換為另一種。

結論

我希望這篇博文能幫助你更好地理解響應式型別。

我們正在 Spring Framework、Spring Boot、Spring Data、Spring Security 和 Spring Cloud 等多個 Spring 專案中,使用 Reactor MonoFlux 等型別來開發響應式支援。

但你即將使用的響應式應用程式也將直接使用這些型別,例如在 @Repository@Service@Controller 方法級別,因為構建響應式應用程式意味著使用響應式語義,你必須處理延遲或流(我們還將提供一些整合阻塞 API 的指導)。

我們將在接下來的幾個月內釋出更多關於響應式的博文。歡迎你透過 這個測試驅動的 Lite Rx API 實戰教程 來熟悉如何使用 FluxMono,一如既往,歡迎提出寶貴意見!

如果你碰巧在五月中旬(無論如何,在巴塞羅那總是個好時候!)在巴塞羅那,請不要錯過參加 Spring I/O 大會的機會。此外,SpringOne Platform(八月初,拉斯維加斯)的註冊最近已經開放,如果你想享受早鳥票價。

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有