理解響應式型別

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

繼之前的 Reactive 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 的過程中,發現有明確的需求需要區分 1 個或 N 個元素的流,這就是 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 的核心關注點是處理背壓(backpressure)。簡而言之,背壓是一種機制,允許接收者請求從發射者那裡接收多少資料。它使得

  • 接收者只有在準備好處理資料時才開始接收
  • 控制正在傳輸的資料量
  • 高效處理慢發射者/快接收者或快發射者/慢接收者的情況
  • 如果你請求 Long.MAX_VALUE 個元素,則可以從動態的推拉(push-pull)策略切換到純粹的推送(push-based)策略

乍一看,Publisher 介面實現起來似乎非常簡單;但完全符合規範地實現它實際上非常困難,而且使用者除了訂閱原始的 Publisher 外,什麼也做不了!這就是為什麼通常最好依賴於 Reactive Streams 的實現(如 Reactor)來幫助你完成這項工作的原因。

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

同樣值得注意的是,向 Reactive Streams 的趨同以及 Reactor 的轉換能力 使得在執行時輕鬆高效地實現響應式型別之間的轉換成為可能。

總結

希望這篇部落格文章能幫助您更好地理解響應式型別。

我們正在 Spring Framework、Spring Boot、Spring Data、Spring Security 和 Spring Cloud 等各種 Spring 專案中努力實現對 Reactor MonoFlux 等響應式型別的支援。

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

我們將在未來幾個月內釋出更多關於響應式的部落格文章。請隨意透過 這個測試驅動的精簡版 Rx API 實操 來熟悉如何使用 FluxMono,一如既往,歡迎您的反饋!

如果您恰好在五月中旬到訪巴塞羅那(無論如何,那都是一個不錯的季節!),不要錯過參加 Spring I/O 大會 的機會。此外,SpringOne Platform(八月初,拉斯維加斯)的註冊也已開放,如果您想享受早鳥票優惠,請抓緊時間。

訂閱 Spring 新聞郵件

透過 Spring 新聞郵件保持聯絡

訂閱

取得領先

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

瞭解更多

獲取支援

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

瞭解更多

即將舉行的活動

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

檢視全部