Spring 響應式程式設計,真香!!!

語言: CN / TW / HK

一、前言

  • 響應式程式設計是啥?
  • 為啥要有響應式程式設計?
  • 響應式流的核心機制是什麼?
  • Spring 響應式程式設計能解決我們平時開發的什麼痛點?
  • Spring 響應式程式設計有哪些應用場景?
  • Spring 響應式程式設計未來的趨勢如何?

開篇六連問,等咱們熟悉完再來真香也不遲,我們廢話少說,直接來暢遊 Spring 響應式程式設計的世界。

二、響應式程式設計是啥?

在計算中,響應式程式設計或反應式程式設計(Reactive programming)是一種面向資料串流和變化傳播的宣告式程式設計正規化。這意味著可以在程式語言中很方便地表達靜態或動態的資料流,而相關的計算模型會自動將變化的值通過資料流進行傳播。

有點抽象?沒有關係,老周這就來說道說道。核心的一點響應式程式設計是宣告式程式設計正規化,對指令式程式設計進行替代的一個範例,這種替代的存在是因為響應式程式設計解決了指令式程式設計的限制。大多數開發者都是指令式程式設計起步的,你寫的程式碼就是一行接一行的指令,按照它們的順序一次一條地出現。一個任務被執行,程式就需要等到它執行完了,才能執行下一個任務。每一步,資料都需要完全獲取到了才能被處理,因此它需要作為一個整體來處理。

指令式程式設計有個最大的弊端是:當正在執行的任務被阻塞了,特別是一個 IO 任務,例如將資料寫入到資料庫或從遠端伺服器獲取資料,那麼呼叫該任務的執行緒將無法做任何事情,直到任務完成。說白了,阻塞的執行緒就是一種浪費,在如今的環境,執行緒的資源是那麼的寶貴。

相反,響應式程式設計是函式式和宣告式的。響應式程式設計涉及描述通過該資料流的 pipeline 或 stream,而不是描述的一組按順序執行的步驟。響應式流處理資料時只要資料是可用的就進行處理,而不是需要將資料作為一個整體進行提供。

三、為啥要有響應式程式設計?

我們上面也說了指令式程式設計會執行緒阻塞,而響應式程式設計是宣告式程式設計正規化的,是對指令式程式設計進行替代的一個範例。

對於指令式程式設計的同步阻塞,其實業界是有一些處理方案的,比如在 Java 中,為了實現非同步非阻塞,一般會採用回撥和 Future 這兩種機制,但這兩種機制都存在一定侷限性。

3.1 回撥機制

我們來看下面這個圖:

在這裡插入圖片描述 服務 B 的 methodB() 方法呼叫服務 A 的 methodA() 方法,然後服務 A 的 methodA() 方法執行完畢後,再主動呼叫服務 B 的 callback() 方法。

回撥體現的是一種雙向的呼叫方式,實現了服務 A 和服務 B 之間的解耦。在這個 callback 回撥方法中,回撥的執行是由任務的結果來觸發的,所以我們就可以非同步來執行某項任務,從而使得呼叫鏈路不發生任何的阻塞。

回撥的最大問題是複雜性,一旦在執行流程中包含了多層的非同步執行和回撥,那麼就會形成一種巢狀結構,給程式碼的開發和除錯帶來很大的挑戰。所以回撥很難大規模地組合起來使用,因為很快就會導致程式碼難以理解和維護,從而造成所謂的“回撥地獄”問題。之前公司就遇到程式碼“回撥地獄”問題,十幾層的回撥,後面的人進來維護估計會吐。

3.2 Future 機制

我們再來看看 Future 這種機制,有一個需要處理的任務,然後把這個任務提交到 Future,Future 就會在一定時間內完成這個任務,而在這段時間內我們可以去做其他事情。下面我們來看看來自 Doug Lea 大神在 Java 中的 Future 介面設計:

在這裡插入圖片描述 我們可以看到,大神在上面的設計來達到一定的非同步執行效果。但從本質上講,Future 以及由 Future 所衍生出來的 CompletableFuture 等各種優化方案就是一種多執行緒技術。多執行緒假設一些執行緒可以共享一個 CPU,而 CPU 時間能在多個執行緒之間共享,這一點就引入了“上下文切換”的概念。

如果想要恢復執行緒,就需要涉及載入和儲存暫存器等一系列計算密集型的操作。因此,大量執行緒之間的相互協作同樣會導致資源利用效率低下。

3.3 響應式程式設計實現方法

3.3.1 資料流與響應式

資料流就是資料像水流一樣源源不斷的輸入過來,而系統的響應能力就體現在對這些資料流的即時響應過程上。我們可以不採用傳統的同步呼叫方式來處理資料,而是由處於資料庫上游的各層元件自動來執行事件,從web到service再到dao層,這個過程就像水流一樣,整個資料傳遞鏈路都應該是採用事件驅動的方式來進行運作的,這個過程都應該是非同步非阻塞的,這就是響應式程式設計的核心特點。

相較傳統開發所普遍採用的“拉”模式,在響應式程式設計下,基於事件的觸發和訂閱機制,這就形成了一種類似“推”的工作方式。說白了,就類似現在的 Kafka 等訊息引擎,大部分都採用事件驅動的 pub/sub 模式的架構。這種模式的最大優勢是生成事件和消費事件的過程是非同步執行的,意味著資源之間的競爭關係較少,故伺服器的響應能力也就越高。

3.3.2 響應式宣言

響應式宣言是一份構建現代雲擴充套件架構的處方。這個框架主要使用訊息驅動的方法來構建系統,在形式上可以達到彈性和韌性,最後可以產生響應性的價值。所謂彈性和韌性,通俗來說就像是橡皮筋,彈性是指橡皮筋可以拉長,而韌性指在拉長後可以縮回原樣。

在這裡插入圖片描述

  • 響應性: :只要有可能,系統就會及時地做出響應。即時響應是可用性和實用性的基石,而更加重要的是,即時響應意味著可以快速地檢測到問題並且有效地對其進行處理。即時響應的系統專注於提供快速而一致的響應時間,確立可靠的反饋上限,以提供一致的服務質量。這種一致的行為轉而將簡化錯誤處理、建立終端使用者的信任並促使使用者與系統作進一步的互動。

  • 韌性:系統在出現失敗時依然保持即時響應性。這不僅適用於高可用的、任務關鍵型系統——任何不具備回彈性的系統都將會在發生失敗之後丟失即時響應性。回彈性是通過複製、遏制、隔離以及委託來實現的。失敗的擴散被遏制在了每個元件內部,與其他元件相互隔離,從而確保系統某部分的失敗不會危及整個系統,並能獨立恢復。每個元件的恢復都被委託給了另一個(外部的)元件,此外,在必要時可以通過複製來保證高可用性。(因此)元件的客戶端不再承擔元件失敗的處理。

  • 彈性: 系統在不斷變化的工作負載之下依然保持即時響應性。反應式系統可以對輸入(負載)的速率變化做出反應,比如通過增加或者減少被分配用於服務這些輸入(負載)的資源。這意味著設計上並沒有爭用點和中央瓶頸,得以進行元件的分片或者複製,並在它們之間分佈輸入(負載)。通過提供相關的實時效能指標,反應式系統能支援預測式以及反應式的伸縮演算法。這些系統可以在常規的硬體以及軟體平臺上實現成本高效的彈性。

  • 訊息驅動:反應式系統依賴非同步的訊息傳遞,從而確保了松耦合、隔離、位置透明的元件之間有著明確邊界。這一邊界還提供了將失敗作為訊息委託出去的手段。使用顯式的訊息傳遞,可以通過在系統中塑造並監視訊息流佇列,並在必要時應用回壓,從而實現負載管理、 彈性以及流量控制。使用位置透明的訊息傳遞作為通訊的手段, 得跨叢集或者在單個主機中使用相同的結構成分和語義來管理失敗成為了可能。非阻塞的通訊使得接收者可以只在活動時才消耗資源,從而減少系統開銷。

問題:訊息驅動與上面提到的事件驅動有啥區別呢?

響應式宣言指出了兩者的區別:“訊息驅動”中訊息資料被送往明確的目的地址,有固定導向;“事件驅動”是事件向達到某個給定狀態的元件發出的訊號,沒有固定導向,只有被觀察的資料。

  • 在一個訊息驅動系統中,可定址的接收者等待訊息的到來然後響應訊息,否則保持休眠狀態,訊息驅動系統專注於可定址的接收者。響應式系統更加關注分散式系統的通訊和協作以達到解耦、非同步的特性,滿足系統的彈性和容錯性,所以響應式系統更傾向於使用訊息驅動模式。
  • 在一個事件驅動系統中,通知的監聽者被繫結到訊息源上。這樣當訊息被髮出時,它就會被呼叫,所以,響應式程式設計更傾向於事件驅動。

下一篇老週會來說下響應式流的核心機制是什麼?敬請期待~