引言
公司物聯網項目集成Orleans以支持高並發的分布式業務,對於Orleans也是第一次接觸,本文就分享下個人對Orleans的理解。
這里先拋出自己的觀點:Orleans 是一個支持有狀態雲生應用/服務水平伸縮的基於Virtual Actor 模型的.NET分布式框架。
下面我會從以下幾個關鍵點,進行闡述:
- 雲生應用的挑戰
- 何為有狀態/無狀態
- 什么是 Actor 模型
- 什么是 Virtual Actor 模型
雲生應用的挑戰
在講雲生應用之前,我們來先講講傳統應用,對於傳統應用常用的三層結構如下圖所示。
隨着業務的發展,數據庫層通常存在瓶頸,為了緩解數據庫的壓力,一般會優先考慮增加一層緩存層。
而隨着業務的繼續發展,高並發、大數據量的應用場景逐漸凸顯。如果繼續在單體應用的基礎上進行擴展,能做的無非是增加消息隊列、異步、讀寫分離等機制進行性能優化。總體而言,優化空間不大,但應用的整體復雜度卻隨着引入的新的技術框架而迅速增加,對於應用的維護,是一個潛在的定時炸彈。
這個時候你可能會想,既然單體應用單機部署不能滿足需求,我可以做集群啊。通過將單體應用按照分層結構進行縱向分離,將數據庫從應用服務器分離,將緩存從應用服務器分離。這樣就可以對分離的各個部分進行分別部署,再借助負載均衡完成集群效應。到這一步,你的應用應該能撐一段時間了。
這個時候,如果回到業務本身去分析,對於一個復雜應用來說,通常的性能瓶頸就是幾個核心服務上。如果能夠對存在性能瓶頸的服務進行伸縮,既能大大提高應用的整體可用性又能提高資源的利用率。那怎么做呢?服務拆分。
雲生應用就是服務拆分的結果,雲生應用最大的特點就是:
- 並行:是指同一時刻能夠處理多個任務。這無可厚非,雲生應用以多個服務形式提供服務,自然是支持並行的。
- 分布式:是指一個應用/服務多次部署,以應對高並發,提升應用/服務的整體性能。
或者簡單來說,雲生應用通過服務拆分支持服務並行,同時各個服務能夠快速伸縮以提升系統吞吐量來應對高並發的業務場景。
雖然通過服務拆分簡化了整個應用的業務復雜度,但是實現的技術復雜度卻只增不減。
有狀態 Vs 無狀態
轉向雲生應用我們面對的第一個難題就是:如何進行服務拆分,才能確保其能分布式部署,或者說是水平伸縮?!
有經驗的同學,可能會立馬想到,要將應用/服務設計為無狀態的。但是這里我要向你討教幾個問題:
- 這個狀態是指什么?
- 何為有狀態?
- 何為無狀態?
大家不妨先停下來思考一下。(歡迎大家在評論中闡述不同觀點。)
這里,我嘗試從以下兩個角度來談下自己的看法:
1. 對象
面向對象編程強調的是對現實事物的抽象和封裝。通過對事物狀態和行為進行抽象然后封裝為對象(類),其中狀態封裝為類的屬性、字段,將行為封裝為類的方法。這個時候得到的對象是沒有生命力的,因為它本質是一個抽象的結果。只有在程序運行中對類進行實例化得到一個對象的實例時,才可以說這個實例對象是有狀態和行為的,因為這個狀態和行為是其獨自持有的,這是一個非常核心的條件。獨自持有,換句話說,就是非共享成員。
獨自持有非共享的成員就可以說這個對象實例是有狀態的嗎?
這里面你就要看清狀態和有狀態的區別!
舉個簡單例子,大街上你看到一大叔開着豪車,你覺得他很富有。“開着豪車”是你即時看到的狀態屬性。“富有”是你的狀態斷言。但這個狀態斷言是一個假設,畢竟可能是借的嘛。
怎樣才能斷定“富有”就是這位大叔擁有的狀態呢?很簡單,假設一年365天你天天見到他開豪車,那基本八九不離十了。
所以,如果認定一個對象是否有狀態,還要看其狀態屬性是否持久化!
如果你同意這個觀點,那么哪天你看我騎個共享單車,氣喘吁吁從你面前經過,就不要簡單認為我是苦逼工薪族。畢竟我也是身價上千萬,只是偶爾騎個車鍛煉鍛煉。(身價上千萬,昨晚夢到的。)
所以,從對象角度看,一個對象是否有狀態的充分必要條件是:
- 對象已實例化(處於運行時)
- 擁有非共享的狀態屬性
- 狀態持久化
那問題來了,我們經常寫的類創建的實例,是有狀態的嗎?
2. 應用
基於上面的總結,我們再來從應用的角度來看分析這個問題。
那應用的狀態和行為是什么?
首先,只有運行中的應用才有狀態和行為。基於這個前提,個人理解運行時應用的狀態是應用持有的數據,行為是應用提供的功能。那應用的有無/無狀態界定就要看運行時應用持有的數據能否持久化。
以簡單的Web分層應用舉例 。從邏輯架構上來講應用一般分為三層,表示層、業務層和數據訪問層。上層進行狀態行為的封裝,數據層提供數據的持久化。所以從整體的角度來看,其是一個有狀態的應用。但單獨來看,我們不能對每一層進行有/無狀態的界定。第一,每一層不能單獨運行;第二,分層的目的是為了職責的隔離,每一層負責相應職責的抽象和封裝,其輸出的是類文件,是對象的集合,沒有生命力。
那從物理架構上來講,Web應用可以分開兩個部分進行部署:Web實例和MySQL實例。也就是說應用和數據庫是可以分開部署的。這個時候Web實例就是無狀態的。那我們一般常說的無狀態服務其實是就是從這個拆分的角度來說的。
Actor 模型
理清完服務拆分的核心問題后,我們不得不來處理第二個棘手的問題:如何解決雲生應用高並發的應用場景呢?
那首先我們需要明確處理高並發的難點在哪?第一個是高性能,第二個就是:資源競爭導致的數據一致性問題。對於第一個難點,通過水平擴展服務可以化解;對於第二個難點,一般就是采用鎖機制,而對於雲生分布式的應用場景下,處理手段就更加復雜,可能需要使用分布式鎖,而這種做法,大大降低了應用的整體響應性能。那有沒有更好的解決方案,既兼顧性能又可以確保數據一致性呢?
有,借助Actor模型。
簡單來講:Actor模型 = 狀態 + 行為 + 消息。一個應用/服務由多個Actor組成,每個Actor都是一個獨立的運行單元,擁有隔離的運行空間,在隔離的空間內,其有獨立的狀態和行為,不被外界干預,Actor之間通過消息進行交互,而同一時刻,每個Actor只能被單個線程執行,這樣既有效避免了數據共享和並發問題,又確保了應用的伸縮性。
另外Actor基於事件驅動模型進行異步通信,性能良好。且位置透明,無論Actor是在本機亦或是在集群中的其他機器,都可以直接進行透明調用。
因此Actor模型賦予了應用/服務的生命力(有狀態)、高並發的處理能力和彈性伸縮能力。
Virtual Actor 模型 與 Orleans
對於Actor模型,業界已經有系列的實現框架,比如Erlang、Akka。然而Actor模型作為一個偏底層的技術框架,對於開發者來說,需要有一定分布式應用的開發經驗,才能用好Actor(包括Actor的生命周期管理,狀態管理等等)。為了進一步簡化分布式編程,微軟的研究人員引入了 Virtual Actor 模型概念,簡單來講Virtual Actor模型是對Actor模型的進一步封裝和抽象。
其與Actor模型的最大的區別在於,Actor的物理實例完全被抽象出來,並由Virtual Actor所在的運行時自動管理。
Orleans 就是作為一款面向.NET的Virtual Actor模型的實現框架,提供了開發者友好的編程方式,簡化了分布式應用的開發成本。在Orleans中Virtual Actor由Grain來體現。
Orleans中核心優勢:開發效率高、透明可伸縮。
開發效率高具體表現為:
- 面向對象的編程范式去實現Grain
- Grain單線程執行
- Grain透明實例化:換句話說,應用無需關注Actor實例的創建、銷毀,可以直接調用Actor提供的方法。Actor的生命周期由Virtual Actor 運行時進行管理,類似GC,可以把Actor理解為完全托管的狀態。
- Grain位置透明:Actor之間通過持有彼此的邏輯引用(非實例引用)進行相互調用,而不需要知道Actor所處的實際位置。
- Grain狀態透明存儲
- 異常的自動傳播
透明可伸縮體現為:
- 應用狀態的隱式細粒度划分
- 自適應的資源管理:Grain的生命周期完全由Orleans 運行時托管。
- 多路通信:Grain的位置透明,Grain之間通過一組固定的TCP鏈接進行多路復用來進行消息傳遞。
- 高效調度
- 顯式異步
最后
這篇文章,就簡單寫到這里,對於Orleans的詳細介紹后續會結合實際項目輸出更系統的應用細節,下次再見。