這篇教程相對比較獨立,主要目的是給大家一點時間去消化上一篇 corda ledger 系列教程 4 節點服務(上) 裏面的設計思想和業務架構,大家一定要動手去看看源代碼,沒有頭緒的可以從我的第一篇教程看起,後臺留言或者在 corda ledger 技術羣提問,同時也爲我們接下來的源碼課程打下基礎,消除一些技術障礙。

大家知道響應式編程最近比較火熱,無論在後端、微服務還是前端框架,都大面積的使用,用 ReactiveX 官網話來說:ReactiveX is more than an API, it\’s an idea and a breakthrough in programming. 而在我們的類區塊鏈系統 corda ledger 中,跨事務、跨 JVM 虛擬機的記賬操作也免不了採用響應式設計。所以今天穿插的爲大家講解一下響應式編程,思路基本上是從觀察者模式講起,然後介紹觀察者模式的演進版本發佈-訂閱模式,最後引入響應式編程,目的是給大家自主深入學習提供一點思路,也爲了後面能更加順暢的理解相關源碼豐富技術背景。

一、觀察者模式

觀察者模式是著名的 GoF 中的二十三個設計模式之一,它描述瞭如何解決重複出現的設計問題,以設計靈活且可重用的面向對象軟件,使得對象更容易實現,更改,測試和重用。 翻譯自維基百科(The Observer design pattern is one of the twenty-three well-known \”Gang of Four\” design patterns that describe how to solve recurring design problems to design flexible and reusable object-oriented software, that is, objects that are easier to implement, change, test, and reuse)

corda ledger 系列教 5 觀察者模式、訂閱-發佈模式和響應式編程

解決的問題:

  1. 對象之間的依賴關係(尤其是一對多的關係),避免出現緊耦合

  2. 確保一個對象的狀態發生變更後,其他依賴於此的對象可以自動的更新此狀態

  3. 確保一個對象擁有通知其他對象的能力

如何解決:

  1. 定義 Subject 和 Observer 兩個對象

  2. 當 Subject 改變其狀態對象 State 時,所有已經註冊的 Observer 對象都會被通知,並且自動更新

職責:

  1. Subject 對象的職責是維護一個包含 Observer 的列表,同時在狀態對象 State 發生變化之後,通過調用 Observer 對象的 update() 方法通知它們

  2. Observer 對象的職責是將自己註冊或者註銷於 Subject 對象(通過調用 Subject 的 attach() 和 detach() 方法)

不足:可以看到,在觀察模式的最基本實現中,Observer 需要顯式的將自己註冊或註銷於 Subject,這裏會有一個叫做失效監聽者的問題 ( lapsed listener problem ) ,甚至會有內存泄漏的危險:由於對象 Subject 顯式的持有一個指向 Observer 的指針,那麼一旦 Observer 在不再需要監聽的時候註銷失敗,那麼它將永遠不會被 GC 系統回收掉。

演進:如上所述,由於 Observer 和 Subject 的緊耦合,除了會有內存泄漏的危險,在可擴展性、速度、信息恢復和可維護性方面都有諸多限制,因此觀察者模式演進成訂閱-發佈模式(publish-subscribe pattern),即在 Observer 和 Subject 之間引入消息隊列、消息處理對象、階段等概念來充分解耦。

二、發佈-訂閱模式

發佈-訂閱模式是消息模式(messaging pattern,也叫事件驅動模式)的一種,同時也是我們通常意義上的消息隊列範式的原型。消息的發送方通常稱爲發佈者(Publisher),消息的接受方稱爲訂閱者(Subscriber),消息實體會有多種類別(Topic)。發佈者和訂閱者並不直接交互,甚至互相都不感知,它們只是單純的生產或者消費相應類別的消息實體,中間的處理(路由和分發)由消息處理對象完成,從而達到充分解耦和橫向擴展等目的。

corda ledger 系列教 5 觀察者模式、訂閱-發佈模式和響應式編程

三、響應式編程

在介紹具體的響應式編程的概念之前,我們先類比思考一下什麼是“響應式”,瞭解交互設計或者前端開發的同學一定對一個詞特別敏感:自適應,意思是我們開發的網頁,不管是在電腦屏幕上,還是在手機瀏覽器,甚至在用戶實時拖動瀏覽器窗口大小時,都能完美無缺的顯示在屏幕上。言下之意,網頁的佈局會隨着瀏覽器行爲的改變而改變,這就是很典型的響應式設計。

如何實現?不妨套用一下上文描述的觀察者模式,讓頁面佈局(Observer,也叫 Subscriber)去訂閱(subscribe)瀏覽器 (Subject,也叫 Obserable)的某一個狀態,比如此處的窗口大小,一旦該狀態發生變化,瀏覽器便會通知網頁佈局,後者便針對新的窗口大小做出改變。

回到代碼層面上,響應式編程實際上屬於異步編程的範疇,Observer 完成對 Obserable 的訂閱動作後並不會阻塞的去等待後者的狀態變化,而是預留一些預處理方法來接收後來異步傳輸過來的信息,我們通常把這些預留的方法稱爲回調函數。描述至此,我們很輕易的能總結出響應式編程的兩個優勢:

  1. 可以併發的去執行相互獨立的計算任務,提高效率

  2. 提高了代碼的抽象程度,增加可維護性

接下來我們響應式編程框架 rxkotlin 來具體闡述,它是衆多實現了響應式編程設計思想的框架之一:

corda ledger 系列教 5 觀察者模式、訂閱-發佈模式和響應式編程

雖然 corda ledger 使用的是 rxjava,但是原理相通,並且 rxkotlin 是基於 rxjava 實現(爲其添加了一些便利的擴展方法),我們來運行一下官方提供的代碼片段來直觀的感受一下用 rxkotlin 進行響應式編程的魅力:

corda ledger 系列教 5 觀察者模式、訂閱-發佈模式和響應式編程

運行結果如下:

corda ledger 系列教 5 觀察者模式、訂閱-發佈模式和響應式編程

上面的代碼可能對初學者不太友好,下面我們一步一步來操作:

一、創建 observer 對象,即觀察者對象

corda ledger 系列教 5 觀察者模式、訂閱-發佈模式和響應式編程

這裏注意幾點:

  1. = object 是 kotlin 中直接定義對象的便捷寫法,省去了定義類再實例化的過程

  2. Observer 是 RxJava 內置接口,這裏我們 override 了它的四個方法,分別在通知完成、註冊、新通知來臨、Obserable 發生錯誤時被回調

二、創建 Observable 對象,並創建三個事件(回想一下上面描述的訂閱-發佈模式)

corda ledger 系列教 5 觀察者模式、訂閱-發佈模式和響應式編程

rxjava 中的操作符很豐富,這裏使用的 create 便是其中之一,更爲詳細的操作符描述請見 http://reactivex.io/documentation/operators.html,後面有機會我會詳細出一篇教程講解

三、訂閱

corda ledger 系列教 5 觀察者模式、訂閱-發佈模式和響應式編程

查看運行結果:

corda ledger 系列教 5 觀察者模式、訂閱-發佈模式和響應式編程

基本脈絡很清晰,首先是創建訂閱者和被訂閱者,訂閱動作發生之後,後面的代碼執行完全是異步的事件驅動的。

最後跟大家探討一下 rxjava 中一個非常重要的問題:線程調度,我們來改寫一下之前的代碼,讓 observer 和 observable 的各個方法都打印一下他們所在的線程,同時我們讓 observable 階段性的產生一些事件:

corda ledger 系列教 5 觀察者模式、訂閱-發佈模式和響應式編程

可以看到,observer 大概每隔一秒鐘生成一個事件,發送一個 float 類型的參數,同時打印出自己當前線程號;另一方面 observer 的幾個回調方法也有打印了它們的線程號,來看看運行的結果:

corda ledger 系列教 5 觀察者模式、訂閱-發佈模式和響應式編程

也就是說,默認情況下 rx 是單線程的,無論是 observable 還是 observer 的方法都會在 subscribe 動作發生的那個線程執行,這往往不可接受,一旦 observer 進行了某些耗時操作阻塞了當前線程,後果很嚴重,來看下面的代碼:

corda ledger 系列教 5 觀察者模式、訂閱-發佈模式和響應式編程

我們在 observer 的 onNext() 方法寫了個死循環,可以看到 observable 就再也不能生成事件了:

corda ledger 系列教 5 觀察者模式、訂閱-發佈模式和響應式編程

幸運的是 rxjava 爲我們提供了 subscribeOn 和 observeOn 分別用來控制 subscription 的調用線程和 接受事件通知(observer 的 onNext/onError/onCompleted 函數)的線程。同時,rxjava 引入了 Schedulers 對象以簡化我們對線程的控制。

再改一下我們代碼,測試一下 subscribeOn 和 observeOn 的用途:

corda ledger 系列教 5 觀察者模式、訂閱-發佈模式和響應式編程

我們把主線程的 id,生成事件線程的 id 以及接收事件通知的線程 id 都打印了出來,同時阻塞主線程 5 秒鐘:

corda ledger 系列教 5 觀察者模式、訂閱-發佈模式和響應式編程

顯然,生成事件和接收事件兩個動作處於不同的線程之中。我們再來對代碼做一點有趣的改動,增加多個 observeOn 函數:

corda ledger 系列教 5 觀察者模式、訂閱-發佈模式和響應式編程

強調一下,這裏的 map 隸屬於抽象類 Observable ,和 kotlin 集合方法 map 方法類似,接收一個函數作爲參數,將自己轉換成另一個 Observable 對象(由於篇幅的原因我沒辦法詳細講解 rxjava 的操作符部分,但實際上這部分對我們利用 rxjava 或者 rxkotlin 寫出優雅的代碼非常有幫助),同時我們也打印出了每個 map 方法所在的線程號,結果如下:

corda ledger 系列教 5 觀察者模式、訂閱-發佈模式和響應式編程

可以看到在遇到 observeOn 之前,所有的 map 操作發生在一個線程,之後在另外一個線程。利用這個特性,我們可以在 Rx 數據流中不同地方設置不同的線程,看一下官方文檔給的一個示意圖和相關的解釋:

corda ledger 系列教 5 觀察者模式、訂閱-發佈模式和響應式編程

As shown in this illustration, the SubscribeOn operator designates which thread the Observable will begin operating on, no matter at what point in the chain of operators that operator is called. ObserveOn, on the other hand, affects the thread that the Observable will use below where that operator appears. For this reason, you may call ObserveOn multiple times at various points during the chain of Observable operators in order to change on which threads certain of those operators operate.

最後總結一下 rxjava 內置的 Schedulers 對象:

  1. ImmediateScheduler 並沒有做任何線程調度。只是同步的執行任務。嵌套調用會導致任務被遞歸執行

  2. TrampolineScheduler 也是同步執行,但是不嵌套任務。而是把後來的任務添加到任務隊列中,等前面的任務執行完了 再執行後面的

  3. NewThreadScheduler 給每個任務創建一個新的線程。

  4. ComputationScheduler 計算線程,用於需要大量 CPU 計算的任務

  5. IOScheduler 用於執行 io 操作密集的任務

再次說明,響應式編程的內容非常豐富,這裏只是給大家一個入門的思路,更加詳細的資料請大家瀏覽 http://reactivex.io/,後面的源碼教程中涉及到具體的細節,仍然會和大家一起學習。從第一篇教程就跟大家分享過,區塊鏈其實是一門組合技術,corda ledger 涉及到的技術範疇也非常豐富,可以這麼說,如果能把 corda ledger 這套系統理解透,不僅僅對學習其他區塊鏈系統有幫助,對其他 IT 技術的學習也大有裨益!所以希望大家認真對待。有任何疑惑或者指正的地方歡迎大家與我們交流 :)