2019年11月6日 星期三

Reactive Programming(一)

本系列的文章為 Modern Java in Action / Project Reactor 的讀書筆記,因此內容可能會有點跳躍。

Reactive Programming
過去的 Java

在 Java 過去的歷史中,可以看到 Java 依循著當代的硬體特性與軟體潮流進行的改變。在最初的時候 Java 只能單一執行緒在執行工作,但隨著 Duo Core 的出現與普及,開發者開始有想要善用多顆核心的需求,讓 CPU 核心盡可能地越忙越好,以此來榨乾系統的效能。Java 在 最開始的時候,就已經提供 Thread、Runnable 等工具讓開發者可以控制 Thread。接著在 2004 年的 Java 5 時釋出 java.util.concurrent,提供 ExecutorService、Callable 與 Future。到這個階段時,Java 已經開始將層次提高,使得開發者不用直接地跟 Thread 搏鬥,而是間接地透過 ExecutorService 去控制 Thread,將專注力從 Thread 的控制拉回到商業邏輯(任務內容)。2014 年的 Java 8 時,CompletableFuture 出現,讓原有的 Future 得以被組合,以像是 Lambda Expression 那樣的形式被串接起來。最後 2017 年的 Java 9,java.util.concurrent.Flow,Reactive 的介面被放入 Java 中了。

快速地回顧這段歷史,可以看到 Java 改變的歷程是跟隨著軟體的潮流在演進,開發者一步一步地從底層往高層前進,從最開始的直接控制 Thread、到抽象化 Thread Pool 發送任務、到完全看不見 Thread Pool 地執行任務,開發者得以越來越專注於自己的工作,而不是花費大量的時間在與底層的 Thread 搏鬥。

Reactive Library 能夠提供什麼?

首先,Reactive Library 通常提供了 Event Loop 的功能,這帶來了兩種主要的好處:

  1. Event Loop 比 Future 等方法更加輕量,使得多執行緒的處理得以更加有效率。
  2. Reactive Library 通常提供了更高階的實作,使得程式設計師可以用更少的程式碼達到相同的結果。舉例來說,Reactor 的範例 [1] 中顯示了使用 Future/CompletableFuture 跟使用 Reactor 的差別,可以明顯看出 Reactor 簡潔得多。

不過在繼續之前,也許先來看看我們是如何「減少系統的閒置時間」的。

Parallelism、Concurrency、Event Loop

在 [2] 當中,對於 Java 的多工處理有以下的描述:

As we discuss in the rest of this chapter, processing streams of events in an asynchronous and nonblocking way is essential for maximizing the use rate of modern multicore CPUs and, more precisely, of the threads competing for their use. To achieve this goal, the reactive frameworks and libraries share threads (relatively expensive and scarce resources) among lighter constructs such as futures; actors; and (more commonly) event loops dispatching a sequence of callbacks intended to aggregate, transform, and manage the events to be processed.

Parallelism、Concurrency


截自 Modern Java in Action [2]

在過去,我們常使用 Parallelism 或者 Concurrency 這兩種手法來提升資源的利用率,也就是透過同時讓多顆核心一起並行工作、或者讓單顆核心在閒置時切換成其他工作的方法,來增加整體系統的效能並避免系統資源的閒置。

Event Loop

Event Loop [5-6] 是另一種概念略為不同的處理方法,基本上它是讓 thread 執行一個無窮迴圈,並且依賴以接收訊息的形式來執行任務。

Reactive?

概略來說,Reactive 大多是以 Event Loop 的形式來提供功能,並且更進一步地抽象化操作。就像當初從 Thread 走入 ExecutorService 時一樣,Reactive 讓開發者可以以更高階的概念去編寫程式碼,而不用過度專注在底層的控制。這使得一般來說,程式碼會變得更加簡潔易懂。

Reactive Manifesto

在 2015 年時,由 Jonas Bonér、Dave Farley、Roland Kuhn、Martin Thompson 等人整理出一系列的規則 [3],表示對於「反應式」的系統應該要具備什麼要件:

  1. Responsive - 系統在任何情況下都必須能夠即時(快速且穩定地)回應。
  2. Resilient - 系統即使在發生錯誤的狀況下,依然能夠維持 Responsive。具體來說,也就是當一部分的系統出錯了,其他跟出錯的部份不相關的元件依然能夠維持正常運作。其中,Resilient 是透過達成 replication、containment、isolation、delegation 這四個特性來滿足的。
  3. Elastic - 系統在負載不斷變化的環境下,依然能夠維持 Responsive。亦即系統可以有效地調配資源,在需要的時候通過 scale-out 增加受到影響的元件的負荷能力。
  4. Message-driven - 要達成 Resilient 與 Elastic,需要系統的元件與元件間清楚地劃分邊界,確保元件與元件是 loose coupling、isolation 以及 location transparency。元件之間需要以非同步的訊息傳遞來進行溝通。

這四個指標其實是由下而上形成的概念,如下圖:


圖片截自 [3]

圖中最下面是 Message Driven,也就是在系統中,每個元件之間要以 Message 來傳遞訊息,而不是直接透過像是 function call。透過 Message,元件會以 Publish–Subscribe Pattern 的形式區分為 Publisher 與 Subscriber 兩種角色,兩種角色互相並不需要知道對方的存在,也因此得以達成元件間的解耦。

基於 Message Driven,我們得以進一步地達成 ElasticResilient。首先元件能夠把錯誤隔離在元件這端,由於跟其他元件的解偶,使得錯誤不會被擴散到系統的其他元件(「其他元件」也包含相同元件的另一個 replication,例如另一個 thread 或者另一個 container 等等),同時對於發生錯誤的元件,系統能夠迅速地將接下來的任務改為委派給相同元件的其他 replication,以達到 Resilient 的特性。同時,系統中的元件隨時都能夠自動地依據系統負載等狀況,調配元件的規模,在需要時增加資源(例如將 thread 多分配一點過來)、不需要時減少資源,達到 Elastic 的特性。

綜合了 Message Driven、Resilient 與 Elastic 的特性之後,整個系統就能夠達成 Responsive。因為元件發生的錯誤會被隔離在元件中,既不會傳染給其他元件、也不會擴散出來讓元件的其他 replication 也發生錯誤,同時每個元件的規模都能夠自由且自動地調整成合適的規模,就能確保整個系統的 responsive:在任何時間點,系統都能快速且穩定地回應。

參考資料
  1. Project Reactor: 3.2. Asynchronicity to the Rescue?
  2. Modern Java in Action
  3. The Reactive Manifesto
  4. The Reactive Manifesto 響應式宣言
  5. Concurrency vs Event Loop vs Event Loop + Concurrency
  6. Understanding Reactor Pattern: Thread-Based and Event-Driven
  7. Fault tolerance and recovery in practice with Akka
  8. Reactive Manifesto
  9. Reactive Programming 一种技术,各自表述

沒有留言: