從react 編程 到 "好萊塢"
- 2019 年 11 月 5 日
- 筆記
本文公眾號來源:美碼師
作者:美碼師
概念
Reactive Programming(響應式編程)已經不是一個新東西了。關於 Reactive 其實是一個泛化的概念,由於很抽象,一些理論性的介紹很容易把人帶到溝里去,包括一些語言框架在實現上也會使用不同的一些概念。
按照 維基百科的解釋:
reactive programming is a declarative programming paradigm concerned with data streams and the propagation of change
意思就是,Reactive Programming 就是一種面向數據流、關注變更的聲明式編程範式。面向數據流比較容易理解,而關注變更則說的應該是數據流的特點,比如來自某個介面元素屬性的變更(前端領域)、又或是某個後端實體的更新事件(日誌)..
以下面的這個函數為例:
c = a + b;
這裡定義了變數c 是 變數a、變數b 之和,當a=1,b=2時,c的值就是3。假設我們在程式中執行了這個語句,那麼對於一次執行過程所產生的c的值就是確定的(上下文中的a、b變數也是確定的) 但是,如果a、b的值是不確定的呢?即這個語句僅僅是定義了變數c與 變數a、b 的計算關係,那麼c 的值就是可變的!
如下:
a=1,b=1,c=2a=2,b=2,c=4a=3,b=2,c=5...
簡言之,c需要動態的由 a、b 共同來決定:當 a、b 的值發生變化時,c 的結果要能及時的做出響應(或者叫反應),以此來保證正確性。
這應該就是 Reactive(響應式) 的由來了,由於變數 a、b的值可能會不斷的變化,於是會形成持續不斷的變更事件,也就是事件流,因此 Reactive 是面向流式處理來設計的。此外,在處理這種"變更的流"時,通常是由非同步通知的方式來完成,因此非同步化也是其特徵之一。
從現有的一些Reactive框架來看,關於下面的定義則更加的貼切:
Reactive編程 是面向數據流的、非同步化的編程範式

與Reactive 相對的是Proactive ,後者是一種同步的、輪詢式的處理方式
面向流設計
首先,有別於面向對象編程的思想,在Reactive 範式裡面,所有的東西都可以當做流,即 Everything is Stream。流(Stream) 被作為響應式編程的基本元素,這和其他的編程範式非常類似:
- 面向對象設計,基本單位是對象
- 面向函數設計,基本單位就是函數
- 響應式設計,基本單位就是流..
那麼流是什麼樣的東西呢?
可以是 用戶輸入、數據結構、快取、動態變數… 等等!可以來自 靜態的數據集合,或是動態的事件流。
案例:MVC
MVC(Model-View-Controller) 是前端設計的標準,這也是用來說明"面向流"的一個很好的例子。

其中,來自於用戶的點擊操作,會被轉換為各種事件傳遞給 Controller 進行處理。在這裡,我們可以認為這些持續不斷的事件形成了"事件流"。比如一個按鈕的點擊事件流如下圖:

在這裡,事件流是按時間排序進行處理的。但你可能會說,這不就是簡單的一個事件處理機制嘛? 別著急,基於響應式流可以做更多的事情,如下圖:

上圖的每個灰框代表了一個處理方法:
- buffer(stream.throttle(250ms)),buffer就是緩衝,而throttle 是節流。這個函數的意思就是對流進行緩衝處理,將250毫秒範圍內發生的事件合併到一起。
- map('length of list'),將合併後的列表進行轉換,輸出為每個列表的長度
- filter(x>=2),即按照>=2的條件進行過濾。
當然,使用傳統的編程方式也完全可以實現這些邏輯,只是相比之下基於響應式流的處理會更加的優雅,所用程式碼也會更少。
上面的這個例子出自於《The introduction to Reactive Programming you've been missing》(英文原文:https://gist.github.com/staltz/868e7e9bc2a7b8c1f754),該文章也獲得非常多的star,至少有一部分可以說明基於MVC的例子來理解響應式還是比較容易的。當然,除了前端領域之外,也很容易將響應式流的思想擴展到各個方面,包括 Web後端、大數據處理、實時流計算等等。
非同步化
非同步化處理是響應式編程的另一個重要特徵,這裡的非同步與我們常說的網路IO非同步化意思上是相同的,與非同步相對的概念是同步。
下面的案例可以很好的解釋兩者的區別:
假設你是一個讀書愛好者,某一天你想看《開國大典》這本書,於是你打電話給圖書館的管理員,詢問館內是否有這本書可以借... 同步的方式,管理員在接到電話之後讓你等一下,然後去圖書室查找一番,幾分鐘後回來再拿起電話告訴你結果; 非同步的方式,管理員把你的電話號碼記下來,然後掛掉電話,後面他查找完了再打回電話給你通知結果。
同步和非同步的區別就在於結果通知的方式不同,很明顯,非同步的方式會顯得更加的人性化和高效。因此,響應式編程通常是採用非同步回調的方式,回調方法的調用和控制則會由響應式框架來完成,對於應用開發來說只需要關注回調方法的實現就可以了。
關於同步、非同步,往往會牽扯到阻塞、非阻塞 這兩個相似的概念,需注意的是 後者的側重點不同:阻塞、非阻塞所關注的是調用者的狀態(是否可以停下來做其他事情)的區別
既然談到了非同步,這裡提一個著名的設計原則:好萊塢原則(Hollywood principle)

don't call us, we'll call you
譯文:不要給我們打電話,我們會給你打電話
在好萊塢,把簡歷遞交給演藝公司後就只有回家等待。由於演藝公司對整個娛樂圈是完全控制的,演員只能被動式的接受公司的差使,只能在需要的環節中完成自己的演出。
好萊塢原則的核心是以通知代替輪詢,其強調的是使用回調來降低模組間的依賴關係,或是提升消息處理效率。
與好萊塢原則相關(延伸)的設計模式有許多:
- Spring 的依賴注入(DI),通過將Bean的定義、依賴關係配置到XML文件中,由容器來完成Bean的自動裝配。這樣控制權就從具體的 Bean轉移到到了容器手上,於是就有了控制反轉IoC(Inversion of Control)一詞。
- Swing UI框架中大肆使用的 觀察者模式(Observer), 我們希望獲知某個UI組件的事件變化,可以添加一個ActionListener。之後Swing將會自動將發生的事件傳遞到我們的回調方法上(actionPerformed)。
- Reactor 響應器模式,基於事件驅動的一種設計模式,其設定了Service Handler負責派發事件,Service Handler同步獲得輸入的事件後,進而分發給相應的Request Handler(多路復用) Reactor 一般是用於NIO的場景,如Netty 的網路處理模型:

注意到了嗎?這些設計模式都不約而同使用了回調!,當然在Reactive 範式中也必然離不開這點。
或許,100 種設計模式中,調整一下角度,可以歸納為10種甚至更少。
響應式宣言
https://www.reactivemanifesto.org/
除了上述的兩大特徵之外,還需要提到的一個東西叫 Reactive Manifesto(響應式宣言),這個是由 Lightbend 公司發起的。它的前身是 Typesafe,大名鼎鼎的Scala 就是其發明的。還有流行的Web後端框架 Playframework 也出自於此。
Playframework 的底層是基於Scala的(可同時支援Java和Scala開發),同時也包含了NIO、Reactive的各種特性,不少國外的企業如Linkin、Verizon 都在使用。
於是,有了響應式宣言之後,Reactive開始得到了正名,隨後的Akka、Rx系列、包括Spring生態 都紛紛加入了這個隊列。

在這個宣言裡面,對於響應式的系統特徵定義了四個特性:
- 及時響應(Responsive):系統能及時的響應請求。
- 有韌性(Resilient):系統在出現異常時仍然可以響應,即支援容錯。
- 有彈性(Elastic):在不同的負載下,系統可彈性伸縮來保證運行。
- 消息驅動(Message Driven):不同組件之間使用非同步消息傳遞來進行交互,並確保松耦合及相互隔離。
在響應式宣言的所定義的這些系統特徵中,無一不與響應式的流有若干的關係,於是乎就有了 2013年發起的 響應式流規範(Reactive Stream Specification)。
https://www.reactive-streams.org/
其中,對於響應式流的處理環節又做了如下定義:
- 具有處理無限數量的元素的能力,即允許流永不結束
- 按序處理
- 非同步地傳遞元素
- 實現非阻塞的負壓(back-pressure)
負壓這個概念或許有些陌生,但本質是為了協調流的處理能力提出的,對於流處理來說會分為 Publisher(發布者) 和Subscriber(訂閱者)兩個角色,可看做生產者與消費者的模式。當發布者產生的消息過快時,訂閱者的處理速度可能會跟不上,此時可能會導致一系列的系統問題。因此負壓的目的就是定義一種回饋機制,讓訂閱者(消費方)向發布者告知其自身的狀態(包括處理速度), 儘可能讓發布方作出調整,本質上是一種系統自我保護的手段。說到這裡,不得不想到TCP的 MTU協商了。
關於Reactive Stream 規範的定義可以參考這篇翻譯:https://github.com/yelf2000/rxjava/wiki/Reative-Streams-%E8%A7%84%E8%8C%83
為什麼要使用Reactive
回答這個問題並不容易,一定是要從 Reactive 編程中獲得一定好處了之後才能解答,當然不同人的看法也不一樣。就筆者淺顯的看法來說,Reactive響應式編程提出了一種更高級的抽象,將數據的處理方式沉澱到可復用的庫之後可以提高開發的效率。
實質上, Reactive響應式始終是一種模式,只是在不同的框架體系中產生了各種五花八門的說法,導致初學者非常容易迷路。光是 Java語言中的 RxJava、Reactor、Java 9 這些不同類庫的介面概念就有不少差異,更不用說跨語言了。