在之前的文章中就討論過為什么在高并發的情況下,程序會崩潰。主要原因是,在高并發的情況下,有大量用戶請求需要程序計算處理,而目前的處理方式是,為每個用戶請求分配一個線程,當程序內部因為訪問數據庫等原因造成線程阻塞時,線程無法釋放去處理其他請求,這樣就會造成請求堆積,不斷消耗資源,最終導致程序崩潰。

這是傳統的 Web 應用程序運行期的線程特性。對于一個高并發的應用系統來說,總是同時有很多個用戶請求到達系統的 Web 容器。Web 容器為每個請求分配一個線程進行處理,線程在處理過程中,如果遇到訪問數據庫或者遠程服務等操作,就會進入阻塞狀態,這個時候,如果數據庫或者遠程服務響應延遲,就會出現程序內的線程無法釋放的情況,而外部的請求不斷進來,導致計算機資源被快速消耗,最終程序崩潰。
那么有沒有不阻塞線程的編程方法呢?
一、反應式編程
答案就是反應式編程。反應式編程本質上是一種異步編程方案,在多線程(協程)、異步方法調用、異步 I/O 訪問等技術基礎之上,提供了一整套與異步調用相匹配的編程模型,從而實現程序調用非阻塞、即時響應等特性,即開發出一個反應式的系統,以應對編程領域越來越高的并發處理需求。
人們還提出了一個反應式宣言,認為反應式系統應該具備如下特質:
- 即時響應,應用的調用者可以即時得到響應,無需等到整個應用程序執行完畢。也就是說應用調用是非阻塞的。
- 回彈性,當應用程序部分功能失效的時候,應用系統本身能夠進行自我修復,保證正常運行,保證響應,不會出現系統崩潰和宕機的情況。
- 彈性,系統能夠對應用負載壓力做出響應,能夠自動伸縮以適應應用負載壓力,根據壓力自動調整自身的處理能力,或者根據自身的處理能力,調整進入系統中的訪問請求數量。
- 消息驅動,功能模塊之間,服務之間,通過消息進行驅動,完成服務的流程。
目前主流的反應式編程框架有 RxJAVA、Reactor 等,它們的主要特點是基于觀察者設計模式的異步編程方案,編程模型采用函數式編程。
觀察者模式和函數式編程有自己的優勢,但是反應式編程并不是必須用觀察者模式和函數式編程。Flower 就是一個純消息驅動,完全異步,支持命令式編程的反應式編程框架。
下面我們就看看 Flower 如何實現異步無阻塞的調用,以及 Flower 這個框架設計使用了什么樣的設計原則與模式。
二、反應式編程框架Flower的基本原理
一個使用 Flower 框架開發的典型 Web 應用的線程特性如下圖所示:

當并發用戶到達應用服務器的時候,Web 容器線程不需要執行應用程序代碼,它只是將用戶的 HTTP 請求變為請求對象,將請求對象異步交給 Flower 框架的 Service 去處理,自身立刻就返回。因為容器線程不做太多的工作,所以只需極少的容器線程就可以滿足高并發的用戶請求,用戶的請求不會被阻塞,不會因為容器線程不夠而無法處理。相比傳統的阻塞式編程,Web 容器線程要完成全部的請求處理操作,直到返回響應結果才能釋放線程;使用Flower 框架只需要極少的容器線程就可以處理較多的并發用戶請求,而且容器線程不會阻塞。
用戶請求交給基于 Flower 框架開發的業務 Service 對象以后,Service 之間依然是使用異步消息通訊的方式進行調用,不會直接進行阻塞式的調用。一個 Service 完成業務邏輯處理計算以后,會返回一個處理結果,這個結果以消息的方式異步發送給它的下一個Service。
傳統編程模型的 Service 之間如果進行調用,被調用的Service 在返回之前,調用的 Service 方法只能阻塞等待。而 Flower 的 Service 之間使用了 AKKA Actor 進行消息通信,調用者的 Service 發送調用消息后,不需要等待被調用者返回結果,就可以處理自己的下一個消息了。事實上,這些 Service 可以復用同一個線程去處理自己的消息,也就是說,只需要有限的幾個線程就可以完成大量的 Service 處理和消息傳輸,這些線程不會阻塞等待。
我們剛才提到,通常 Web 應用主要的線程阻塞,是因為數據庫的訪問導致的線程阻塞。Flower 支持異步數據庫驅動,用戶請求數據庫的時候,將請求提交給異步數據庫驅動,立刻就返回,不會阻塞當前線程,異步數據庫訪問連接遠程的數據庫,進行真正的數據庫操作,得到結果以后,將結果以異步回調的方式發送給 Flower 的 Service 進行進一步的處理,這個時候依然不會有線程被阻塞。
也就是說,使用 Flower 開發的系統,在一個典型的 Web 應用中,幾乎沒有任何地方會被阻塞,所有的線程都可以被不斷地復用,有限的線程就可以完成大量的并發用戶請求,從而大大地提高了系統的吞吐能力和響應時間,同時,由于線程不會被阻塞,應用就不會因為并發量太大或者數據庫處理緩慢而宕機,從而提高了系統的可用性。
Flower 框架實現異步無阻塞,一方面是利用了 Web 容器的異步特性,主要是 Servlet3.0以后提供的 AsyncContext,快速釋放容器線程;另一方面是利用了異步的數據庫驅動以及異步的網絡通信,主要是 HttpAsyncClient 等異步通信組件。而 Flower 框架內,核心的應用代碼之間的異步無阻塞調用,則是利用了 Akka 的 Actor 模型實現。
Akka Actor 的異步消息驅動實現如下:

一個 Actor 向另一個 Actor 進行通訊的時候,當前 Actor 就是一個消息的發送者sender,當它想要向另一個 Actor 進行通訊的時候,就需要獲得另一個 Actor 的ActorRef,也就是一個引用,通過引用進行消息通信。而 ActorRef 收到消息以后,會將這個消息放入到目標 Actor 的 Mailbox 里面去,然后就立即返回了。
也就是說一個 Actor 向另一個 Actor 發送消息的時候,不需要另一個 Actor 去真正地處理這個消息,只需要將消息發送到目標 Actor 的 Mailbox 里面就可以了。自己不會被阻塞,可以繼續執行自己的操作,而目標 Actor 檢查自己的 Mailbox 中是否有消息,如果有消息,Actor 則會在從 Mailbox 里面去獲取消息,對消息進行異步的處理,而所有的 Actor會共享線程,這些線程不會有任何的阻塞。
三、反應式編程框架Flower的設計方法
但是直接使用 Actor 進行編程有很多不便,Flower 框架對 Actor 進行了封裝,開發者只需要編寫一些細粒度的 Service,這些 Service 會被包裝在 Actor 里面,進行異步通信。Flower Service 例子如下:
publicclassServiceAimplementsService<Message2>{
@Override
publicObjectprocess(Message2message){
returnmessage.getAge()+1;
}
}
每個 Service 都需要實現框架的 Service 接口的 process 方法,process 方法的輸入參數就是前一個 Service process 方法的返回值,這樣只需要將 Service 編排成一個流程,Service 的返回值就會變成 Actor 的一個消息,被發送給下一個 Service,從而實現Service 的異步通信。
Service 的流程編排有兩種方式,一種方式是編程實現,如下:
getServiceFlow().buildFlow("ServiceA","ServiceB");
表示 ServiceA 的返回值將作為消息發送給 ServiceB,成為 ServiceB 的輸入值,這樣兩個Service 就可以合作完成一些更復雜的業務邏輯。
Flower 還支持可視化的 Service 流程編排,像下面這張圖一樣編輯流程定義文件,就可以開發一個異步業務處理流程。

那么這個 Flower 框架是如何實現的呢?
Flower 框架的設計也是基于依賴倒置原則。所有應用開發者實現的Service 類都需要包裝在 Actor 里面進行異步調用,但是 Actor 不會依賴開發者實現的Service 類,開發者也不會依賴 Actor 類,他們共同依賴一個 Service 接口,這個接口是框架提供的,如上面例子所示。
Actor 與 Service 的依賴倒置關系如下圖所示:

每個 Actor 都依賴一個 Service 接口,而具體的 Service 實現類,比如 MyService,則實現這個 Service 接口。在運行期實例化 Actor 的時候,這個接口被注入具體的 Service 實現類,比如 MyService。在 Flower 中,調用 MyService 對象,其實就是給包裝MyService 對象的 Actor 發消息,Actor 收到消息,執行自己的 onReceive 方法,在這個方法里,Actor 調用 MyService 的 process 方法,并將 onReceive 收到的 Message 對象當做 process 的輸入參數傳入。
process 處理完成后,返回一個 Object 對象。Actor 會根據編排好的流程,獲取MyService 在流程中的下一個 Service 對應的 Actor,即 nextServiceActor,將 process返回的 Object 對象當做消息發送給這個 nextServiceActor。這樣,Service 之間就根據編排好的流程,異步、無阻塞地調用執行起來了。
四、反應式編程框架Flower的落地效果
Flower 框架在部分項目中落地應用,應用效果較為顯著,一方面,Flower 可以顯著提高系統的性能。這是某個 C# 開發的系統使用 Flower 重構后的 TPS 性能比較,使用 Flower 開發的系統 TPS 差不多是原來 C# 系統的兩倍。

另一方面,Flower 對系統可用性也有較大提升,目前常見互聯網應用架構如下圖:

用戶請求通過網關服務器調用微服務完成處理,那么當有某個微服務連接的數據庫查詢執行較慢時,如圖中服務 1,那么按照傳統的線程阻塞模型,就會導致服務 1 的線程都被阻塞在這個慢查詢的數據庫操作上。同樣的,網關線程也會阻塞在調用這個延遲比較厲害的服務1 上。
最終的效果就是,網關所有的線程都被阻塞,即使是不調用服務 1 的用戶請求也無法處理,最后整個系統失去響應,應用宕機。使用阻塞式編程,實際的壓測效果如下,當服務 1響應延遲,出錯率大幅飆升的時候,通過網關調用正常的服務 2 的出錯率也非常高。

使用 Flower 開發的網關,實際壓測效果如下,同樣服務 1 響應延遲,出錯率極高的情況下,通過 Flower 網關調用服務 2 完全不受影響。

五、總結
事實上,Flower 不僅是一個反應式 Web 編程框架,還是反應式的微服務框架。也就是說,Flower 的 Service 可以遠程部署到一個 Service 容器里面,就像我們現在常用的微服務架構一樣。Flower 會提供一個獨立的 Flower 容器,用于啟動一些 Service,這些Service 在啟動了以后,會向注冊中心進行注冊,而且應用程序可以將這些分布式的Service 進行流程編排,得到一個分布式非阻塞的微服務系統。整體架構和主流的微服務架構很像,主要的區別就是 Flower 的服務是異步的,通過流程編排的方式進行服務調用,而不是通過接口依賴的方式進行調用。