《基于Actor的響應式編程》計劃分為三部分,第一部分剖析響應式編程的本質思想,為大家介紹何謂響應式編程(Reactive Programming)。第二部分則結合兩個案例來講解如何在AKKA中實現響應式編程。第三部分則是這個主題的擴展,在介紹Reactive Manifesto的同時,介紹進行響應式編程更為主流的ReactiveX框架。本文是這個系列的第一部分。
響應式編程(Reactive Programming)到底是什么?從名詞定義來講,中文的響應式并沒有很好地展現Reactive的本意。響應這個詞語是一個中性詞,本身沒有任何傾向。Reactive Programming強調的是“響應迅速”,響應用戶的請求要如電光火石一般迅捷,做到一觸即發。
傳統的順序編程采用每條指令依次執行的方式,倘若上一條指令沒有執行結束,當前的線程就得等著,任你如何提升機器性能還是代碼性能,如果本質不變,始終改變不了響應需要等待的現實。若要響應迅速,就得把順序執行指令的方式換一換——同步換成異步,方法執行換做消息發送,于是乎,我們可以精簡地定義:
響應式編程就是異步數據流編程。
這其實是一種編程范式,是編程理念的一種思想轉型。因為采用響應式編程,我們就不再將軟件要處理的業務視為對象,又或者函數,而是直接透析到本質:數據流(Data Stream)。
一言以蔽之:萬事萬物皆為流。
我這么說,可能有些絕對。響應式編程并非銀彈,也非你手中四處尋找釘子來敲打的錘子。我們須得結合著實際的場景,考慮是否選擇響應式編程這種范式。然而,如果我們局限在響應式編程的語境下,我們確乎可以視萬事萬物為流。
從函數式編程的角度來講,一連串組合函數的調用,其實就是數據在流動。函數可以抽象地視為一種數據類型到另一種數據類型的轉換。將各種形式的轉換(map、flatMap、filter)穿起來,同時保證數據的不變性(Immutable),則數據就能非常可靠地在函數鏈條中流動,或者被分析,或者被轉換,或者被過濾。
回到業務世界。我們幾乎可以將所有業務處理流程都可以建模為數據流的形式。這種流動差不多可以歸納為:
Command -> CommandHandler -> Event -> EventHandler -> Command ...
這是一種頗有節奏感的“建模儀式”。按照CQRS的設計思想,任何業務都可以分解為兩種形式的消息:Query與Command。Query模型相對簡單,因為它本質上就是一個沒有副作用的只讀操作。執行Command本身是要改變業務對象值的,然而,如果我們將每次變更都視為是一種“狀態的遷移”,然后利用事件去記錄每次變更,就可以將可變轉換為不變。如果熟悉git,就會發現git的版本管理與此建模思想不謀而合。無論是新增、修改還是刪除代碼,都可以視為是一次全新的提交。
當我們將編程的范式切換為“流(Stream)”時,我們欣喜地發現,這種方式可以在很大程度上確保數據是不變的。這就為并行開發創造了可能。
然而,普通的數據流編程范式并不能滿足“響應式Reactive”的本初定義。我們需要響應迅速。如何才能做到?那就是要做到沒有阻塞,這就是我們通常所說的異步工作方式。
因而,響應式編程的設計原則是:
- 保持數據的不變性
- 沒有共享
- 阻塞是有害的
這或許也可以視為是響應式編程的特征,恰好,這三條特征也是Actor模型擁有的。
那么,什么是Actor模型?溯源Actor模型,它由Carl Hewitt在1973年提出,之后在Erlang OTP(Open Telecom Platform)中被廣泛應用在并發編程上。最初的Scala語言也實現了簡單的Actor模型,但隨著AKKA框架的推出,Scala放棄了自身的Actor,轉而選擇使用AKKA。
在《Scala并發編程》一書中,Aleksandar Prokopec形象地描述了Actor系統:
Actor系統模仿了人類的組織,如公司、政府和其他大型機構。在軟件公司中,有許多需要以并發方式達成的目標。為了實現這些目標,數百或數千名員工一起努力工作,而且這些員工通常會被組織成一種層次結構。許多員工會為級別比他們低的員工分派工作。為了高效地工作和決策,員工們使用電子郵件進行通信。
當員工早上上班時,就會檢查他的電子郵箱并對重要的消息做出回應。如果某封電子郵件非常重要,那么這個員工就必須立刻回復這封郵件。當員工忙著回復一封電子郵件時,可能會收到另一封電子郵件,而且后續的電子郵件都會進入他的電子郵箱中。只有當員工處理完成當前的電子郵件后,他才能繼續處理下一封電子郵件。
在這個隱喻中,軟件公司就相當于是一個ActorSystem,每位員工則是一個一個Actor。電子郵件是Actor之間彼此發送的消息(Message),一旦發送了消息,就不必等待收件人的回復,可以繼續自己的工作,也就是說這種消息發送的方式是異步非阻塞的。Actor持有的MailBox正好借用了這里所謂的電子郵箱概念。
因而對于每個Actor而言:
- 每個Actor都擁有獨立的MailBox;
- 接收到的消息皆為不可變對象,且完全獨立;
- 不管是tell消息還是ask消息,Actor執行消息的方式都是異步非阻塞的。
金風玉露一相逢,從某種意義上講,Actor模型就是響應式編程苦苦追尋的良緣佳配。二者天生匹配,且Actor模型的分布式特性還能更好地加強響應式編程的響應與處理速度。