響應式程式設計筆記 第一部分:響應式概覽

工程 | Dave Syer | 2016 年 6 月 7 日 | ...

響應式程式設計(又)變得有趣起來,目前圍繞它有很多討論,對於像筆者這樣普通的外部企業 Java 開發者來說,並非所有討論都易於理解。本文(本系列的第一篇)也許能幫助你理清這些討論的重點。本文將盡量具體化,不涉及“指稱語義學”。如果你想尋找更學術性的方法和大量的 Haskell 程式碼示例,網上有很多,但你可能來錯地方了。

響應式程式設計經常被混淆為併發程式設計和高效能,以至於很難區分這些概念,而實際上它們在原則上完全不同。這不可避免地會導致混淆。響應式程式設計也經常被稱為或混淆為函式式響應式程式設計,即 FRP(我們在本文中互換使用這兩個術語)。有些人認為響應式並非新事物,而且他們每天都在使用(主要是使用 JavaScript)。另一些人似乎認為它是微軟的恩賜(他們在一段時間前釋出了一些 C# 擴充套件時對此大肆宣傳)。在企業級 Java 領域,最近出現了一些熱議(例如,參見響應式流規範),就像任何閃亮的新事物一樣,關於何時何地可以使用和應該使用它,會犯很多簡單的錯誤。

它是什麼?

響應式程式設計是一種微架構風格,涉及事件的智慧路由和消費,所有這些結合起來改變行為。這有點抽象,你在網上遇到的許多其他定義也是如此。接下來,我們將嘗試建立一些更具體的概念,說明什麼是響應式,或者為什麼它可能很重要。

響應式程式設計的起源可能可以追溯到 20 世紀 70 年代甚至更早,所以這個概念本身並不新鮮,但它們確實在現代企業中產生了共鳴。這種共鳴並非偶然,它與微服務的興起以及多核處理器的普及同時到來。希望這樣做的原因能變得清晰一些。

以下是一些來自其他來源的精簡定義

響應式程式設計背後的基本思想是存在某些資料型別,它們表示一個“隨時間變化”的值。涉及這些隨時間變化的值的計算本身也會產生隨時間變化的值。

以及…​

一個很容易理解它的直觀方法是,想象你的程式是一個電子表格,你的所有變數都是單元格。如果電子表格中的任何單元格發生變化,引用該單元格的所有單元格也會隨之變化。FRP 也是如此。現在想象一下,有些單元格會自行變化(或者更確切地說,來自外部世界):在 GUI 環境中,滑鼠的位置就是一個很好的例子。

(摘自Stackoverflow 上的術語問題)

FRP 與高效能、併發、非同步操作和非阻塞 IO 有很強的關聯性。然而,也許最好先懷疑一下 FRP 與這些概念毫無關係。在使用響應式模型時,這些問題確實可以自然地處理,並且通常對呼叫者是透明的。但是,在有效或高效處理這些問題方面的實際收益,完全取決於具體的實現(因此應該受到高度審視)。同樣,也可以以同步、單執行緒的方式實現一個完全合理且有用的 FRP 框架,但這對於嘗試使用任何新工具和庫來說,可能並沒有真正的幫助。

響應式應用場景

作為一個新手,似乎最難回答的問題是“它有什麼用?” 這裡有一些企業環境下的例子,說明了它的一般使用模式

外部服務呼叫 現今許多後端服務都是 REST-ful(即它們透過 HTTP 操作),因此底層協議本質上是阻塞和同步的。這可能不是 FRP 的明顯領域,但實際上它是相當肥沃的土壤,因為這些服務的實現通常涉及呼叫其他服務,然後又依賴於第一次呼叫的結果來呼叫更多服務。如果 IO 如此頻繁,而你在傳送下一個請求之前等待一個呼叫完成,那麼在你組織回覆之前,你的可憐客戶端就會沮喪地放棄。因此,外部服務呼叫,尤其是呼叫之間複雜的依賴編排,是一個很好的最佳化目標。FRP 提供了驅動這些操作的邏輯的“可組合性”承諾,使得呼叫服務的開發者更容易編寫程式碼。

高併發訊息消費者 訊息處理,特別是在高併發的情況下,是企業常見的用例。響應式框架喜歡測量微基準測試,並吹噓在 JVM 中每秒可以處理多少訊息。結果確實驚人(每秒數千萬條訊息很容易實現),但也可能有點人工製造——如果他們說正在測試一個簡單的“for”迴圈,你就不會那麼驚訝了。然而,我們不應過早否定這些工作,很明顯,當效能至關重要時,所有貢獻都應欣然接受。響應式模式與訊息處理天然契合(因為事件可以很好地轉化為訊息),因此如果有一種方法可以更快地處理更多訊息,我們應該予以關注。

電子表格 也許它並非真正的企業用例,但卻是企業中的每個人都能輕鬆理解的例子,它很好地抓住了 FRP 的理念和實現難度。如果單元格 B 依賴於單元格 A,而單元格 C 同時依賴於單元格 A 和 B,那麼如何傳播 A 中的變化,確保在向 B 傳送任何變化事件之前更新 C 呢?如果你有一個真正的響應式框架作為基礎,那麼答案是“你不用管,你只需要宣告依賴關係”,這就是電子表格力量的精髓所在。它也突出了 FRP 和簡單事件驅動程式設計之間的區別——它賦予了“智慧路由”以“智慧”。

(非)同步處理的抽象 這更多是一個抽象的用例,因此有點偏離了我們也許應該避免的領域。它與前面提到的一些更具體的用例之間也存在一些(很多)重疊,但希望這仍然值得討論。其基本論點是一個熟悉(且合理)的觀點:只要開發者願意接受額外的抽象層,他們就可以不必關心所呼叫的程式碼是同步還是非同步的。由於處理非同步程式設計會消耗寶貴的腦力,因此這裡面可能有一些有用的想法。響應式程式設計不是解決這個問題唯一的方法,但 FRP 的一些實現者對這個問題進行了深入思考,他們的工具很有用。

這篇 Netflix 部落格有一些非常有用的真實世界用例具體示例:Netflix 技術部落格:使用 RxJava 在 Netflix API 中實現函式式響應式

比較

如果你不是自 1970 年以來一直住在山洞裡,那麼你肯定遇到過一些與其他響應式程式設計及其試圖解決的問題相關的概念。以下是其中幾個概念以及我個人對其相關性的看法

Ruby Event-Machine Event Machine 是對併發程式設計(通常涉及非阻塞 IO)的抽象。Ruby 開發者長期以來一直在努力將一種設計用於單執行緒指令碼的語言轉變為可以用來編寫伺服器應用程式的工具,這些應用程式需要 a) 工作正常,b) 效能良好,c) 在負載下保持穩定。Ruby 早已有了執行緒,但使用不多且名聲不佳,因為它並非總能很好地工作。另一種選擇是 Fibers(原文如此),它現在已經普及,並被提升(在 Ruby 1.9 中)到語言的核心。Fiber 程式設計模型有點像協程(參見下文)的一種變體,其中使用單個原生執行緒來處理大量併發請求(通常涉及 IO)。程式設計模型本身有點抽象且難以理解,因此大多數人使用包裝器,而 Event Machine 是最常見的。Event Machine 不一定使用 Fibers(它抽象了這些問題),但在 Ruby Web 應用中很容易找到使用 Event Machine 與 Fibers 的程式碼示例(例如,參考 Ilya Grigorik 的這篇文章,或來自 em-http-request 的 Fibers 示例)。人們這樣做主要是為了在 IO 密集型應用程式中使用 Event Machine 來獲得可伸縮性的好處,同時避免大量巢狀回撥帶來的醜陋程式設計模型。

Actor 模型 與面向物件程式設計類似,Actor 模型是計算機科學中一個可追溯到 20 世紀 70 年代的深入探討領域。Actor 提供了一種計算抽象(與資料和行為相對),這使得併發成為一種自然的推論,因此在實踐中,它們可以構成併發系統的基礎。Actor 之間相互發送訊息,所以在某種意義上它們是響應式的,以 Actor 或響應式風格構建的系統之間有很多重疊。通常,區別在於它們的實現層面(例如,Akka 中的 Actor 可以跨程序分佈,這是該框架的一個顯著特性)。

延遲結果 (Futures) Java 1.5 引入了一套豐富的新庫,包括 Doug Lea 的 "java.util.concurrent",其中一部分就是延遲結果的概念,封裝在 Future 中。它是對非同步模式進行簡單抽象的一個很好的例子,它不強制實現必須是非同步的,也不強制使用任何特定的非同步處理模型。正如Netflix 技術部落格:使用 RxJava 在 Netflix API 中實現函式式響應式很好地展示的,Futures 在只需要併發處理一組類似任務時非常有用,但一旦其中任何任務需要相互依賴或條件執行,就會陷入“巢狀回撥地獄”。響應式程式設計提供瞭解決之道。

Map-reduce 和 Fork-Join 對並行處理的抽象很有用,並且有許多例子可供選擇。Map-reduce 和 Fork-Join 是 Java 世界中最近演進的概念,它們受到了大規模並行分散式處理(MapReduceHadoop)以及 JDK 自身 1.7 版本(Fork-Join)的驅動。這些抽象很有用,但(就像延遲結果一樣)與 FRP 相比它們是淺層的。FRP 可以用作簡單並行處理的抽象,但它超越了這一點,達到了可組合性和宣告式通訊的層面。

協程 “協程” 是“子程式”的一種泛化——它像子程式一樣有一個入口點和多個出口點,但當它退出時,它將控制權傳遞給另一個協程(不一定是它的呼叫者),並且它累積的任何狀態都會保留並記住,以便下次呼叫時使用。協程可以作為構建更高階特性(如 Actor 和 Stream)的基礎。響應式程式設計的目標之一是為並行處理的通訊代理提供類似的抽象,因此協程(如果可用)是一個有用的構建塊。協程有各種變體,其中一些比通用情況更具限制性,但比普通的子程式更靈活。Fibers(參見關於 Event Machine 的討論)是一種變體,而 Generator(在 Scala 和 Python 中常見)是另一種。

Java 中的響應式程式設計

從原生不支援協程的角度來看,Java 不是一種“響應式語言”。JVM 上還有其他語言(Scala 和 Clojure)更原生地支援響應式模型,但 Java 本身直到版本 9 才支援。然而,Java 是企業開發的強大力量,最近有很多活動致力於在 JDK 之上提供響應式層。我們在這裡只對其中幾個進行非常簡要的介紹。

響應式流規範 是一個非常低層次的契約,以少量 Java 介面(加上一個 TCK)的形式表達,但也適用於其他語言。這些介面表達了具有顯式背壓(back pressure)的 PublisherSubscriber 的基本構建塊,為可互操作的庫形成了一種通用語言。響應式流規範已在版本 9 中作為 java.util.concurrent.Flow 整合到 JDK 中。該專案是 Kaazing、Netflix、Pivotal、Red Hat、Twitter、Typesafe 以及許多其他公司的工程師共同協作的成果。

RxJava:Netflix 一段時間以來內部一直在使用響應式模式,然後他們將使用的工具以開源許可證釋出為 Netflix/RxJava(後來更名為“ReactiveX/RxJava”)。Netflix 大量使用 Groovy 在 RxJava 之上進行程式設計,但它也支援 Java 使用,並且透過使用 Lambda 表示式非常適合 Java 8。存在一個連線到響應式流規範的橋接。根據 David Karnok 的響應式庫代系分類,RxJava 是一個“第二代”庫。

Reactor 是由 Pivotal 開源團隊(即建立 Spring 的團隊)開發的一個 Java 框架。它直接基於響應式流規範構建,因此無需橋接。Reactor IO 專案提供了對 Netty 和 Aeron 等低階網路執行時環境的封裝。根據 David Karnok 的響應式庫代系分類,Reactor 是一個“第四代”庫。

Spring Framework 5.0(第一個里程碑版本於 2016 年 6 月釋出)內建了響應式特性,包括構建 HTTP 伺服器和客戶端的工具。現有的 Web 層 Spring 使用者會發現一個非常熟悉的程式設計模型,使用註解修飾控制器方法來處理 HTTP 請求,大部分響應式請求的分發和背壓問題都交給框架處理。Spring 基於 Reactor 構建,但也暴露了允許使用多種庫(例如 Reactor 或 RxJava)來表達其特性的 API。使用者可以從 Tomcat、Jetty、Netty(透過 Reactor IO)和 Undertow 中選擇伺服器端網路棧。

Ratpack 是一套用於構建高效能 HTTP 服務的庫。它基於 Netty 構建,並實現了響應式流規範以實現互操作性(因此你可以在棧中更上層使用其他的響應式流規範實現,例如)。Spring 作為原生元件得到支援,可以使用一些簡單的工具類提供依賴注入。還存在一些自動配置,使得 Spring Boot 使用者可以將 Ratpack 嵌入到 Spring 應用程式中,從而啟動一個 HTTP 端點並在那裡監聽,而不是使用 Spring Boot 直接提供的嵌入式伺服器之一。

Akka 是一個用於在 Scala 或 Java 中使用 Actor 模式構建應用程式的工具包,使用 Akka Streams 進行程序間通訊,並且內建了響應式流規範契約。根據 David Karnok 的響應式庫代系分類,Akka 是一個“第三代”庫。

為何是現在?

是什麼推動了企業級 Java 中響應式的興起?嗯,這不僅僅是(全部)技術時尚——人們趕時髦,追逐閃亮的新玩具。驅動力是高效的資源利用,換句話說,就是在伺服器和資料中心上花費更少的錢。響應式承諾你可以用更少的資源做更多的事情,具體來說,你可以用更少的執行緒處理更高的負載。這就是響應式與非阻塞、非同步 I/O 的交集凸顯出來的地方。對於正確的問題,效果是顯著的。對於錯誤的問題,效果可能會適得其反(你實際上讓事情變得更糟)。此外,請記住,即使你選擇了正確的問題,也沒有免費的午餐,響應式並不能幫你解決問題,它只是給你提供了一個工具箱,你可以用它來實現解決方案。

結論

在本文中,我們對響應式運動進行了非常廣泛和高層次的探討,並將其置於現代企業的背景下。JVM 上有許多響應式庫或框架,它們都處於積極開發中。很大程度上,它們提供相似的功能,但越來越多地,得益於響應式流規範,它們能夠互操作。在本系列下一篇文章中,我們將深入實際,檢視一些真實的程式碼示例,以便更好地瞭解響應式的具體含義及其重要性。我們還將花一些時間來理解 FRP 中“F”為何重要,以及背壓和非阻塞程式碼的概念如何深刻影響程式設計風格。最重要的是,我們將幫助你做出重要決定:何時以及如何轉向響應式,以及何時堅持使用舊的風格和技術棧。

獲取 Spring 新聞郵件

訂閱 Spring 新聞郵件,保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助你快速提升。

瞭解更多

獲取支援

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

瞭解更多

即將舉行的活動

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

檢視全部