.NET分散式框架 | Orleans 知多少

「聖傑」發表於2019-07-10

.NET分散式框架 | Orleans 知多少

引言

公司物聯網專案整合Orleans以支援高併發的分散式業務,對於Orleans也是第一次接觸,本文就分享下個人對Orleans的理解。

這裡先丟擲自己的觀點:Orleans 是一個支援有狀態雲生應用/服務水平伸縮的基於Virtual Actor 模型的.NET分散式框架。

下面我會從以下幾個關鍵點,進行闡述:

  1. 雲生應用的挑戰
  2. 何為有狀態/無狀態
  3. 什麼是 Actor 模型
  4. 什麼是 Virtual Actor 模型

雲生應用的挑戰

在講雲生應用之前,我們來先講講傳統應用,對於傳統應用常用的三層結構如下圖所示。
傳統應用三層結構

隨著業務的發展,資料庫層通常存在瓶頸,為了緩解資料庫的壓力,一般會優先考慮增加一層快取層。

增加了快取層的四層結構

而隨著業務的繼續發展,高併發、大資料量的應用場景逐漸凸顯。如果繼續在單體應用的基礎上進行擴充套件,能做的無非是增加訊息佇列、非同步、讀寫分離等機制進行效能優化。總體而言,優化空間不大,但應用的整體複雜度卻隨著引入的新的技術框架而迅速增加,對於應用的維護,是一個潛在的定時炸彈。

這個時候你可能會想,既然單體應用單機部署不能滿足需求,我可以做叢集啊。通過將單體應用按照分層結構進行縱向分離,將資料庫從應用伺服器分離,將快取從應用伺服器分離。這樣就可以對分離的各個部分進行分別部署,再借助負載均衡完成叢集效應。到這一步,你的應用應該能撐一段時間了。

這個時候,如果回到業務本身去分析,對於一個複雜應用來說,通常的效能瓶頸就是幾個核心服務上。如果能夠對存在效能瓶頸的服務進行伸縮,既能大大提高應用的整體可用性又能提高資源的利用率。那怎麼做呢?服務拆分。

雲生應用就是服務拆分的結果,雲生應用最大的特點就是:

  • 並行:是指同一時刻能夠處理多個任務。這無可厚非,雲生應用以多個服務形式提供服務,自然是支援並行的。
  • 分散式:是指一個應用/服務多次部署,以應對高併發,提升應用/服務的整體效能。

或者簡單來說,雲生應用通過服務拆分支援服務並行,同時各個服務能夠快速伸縮以提升系統吞吐量來應對高併發的業務場景。

雖然通過服務拆分簡化了整個應用的業務複雜度,但是實現的技術複雜度卻只增不減。

有狀態 Vs 無狀態

轉向雲生應用我們面對的第一個難題就是:如何進行服務拆分,才能確保其能分散式部署,或者說是水平伸縮?!

有經驗的同學,可能會立馬想到,要將應用/服務設計為無狀態的。但是這裡我要向你討教幾個問題:

  1. 這個狀態是指什麼?
  2. 何為有狀態?
  3. 何為無狀態?

大家不妨先停下來思考一下。(歡迎大家在評論中闡述不同觀點。)

這裡,我嘗試從以下兩個角度來談下自己的看法:

1. 物件

物件導向程式設計強調的是對現實事物的抽象和封裝。通過對事物狀態和行為進行抽象然後封裝為物件(類),其中狀態封裝為類的屬性、欄位,將行為封裝為類的方法。這個時候得到的物件是沒有生命力的,因為它本質是一個抽象的結果。只有在程式執行中對類進行例項化得到一個物件的例項時,才可以說這個例項物件是有狀態和行為的,因為這個狀態和行為是其獨自持有的,這是一個非常核心的條件。獨自持有,換句話說,就是非共享成員。
獨自持有非共享的成員就可以說這個物件例項是有狀態的嗎?
這裡面你就要看清狀態有狀態的區別!
舉個簡單例子,大街上你看到一大叔開著豪車,你覺得他很富有。“開著豪車”是你即時看到的狀態屬性。“富有”是你的狀態斷言。但這個狀態斷言是一個假設,畢竟可能是借的嘛。
怎樣才能斷定“富有”就是這位大叔擁有的狀態呢?很簡單,假設一年365天你天天見到他開豪車,那基本八九不離十了。

所以,如果認定一個物件是否有狀態,還要看其狀態屬性是否持久化!

如果你同意這個觀點,那麼哪天你看我騎個共享單車,氣喘吁吁從你面前經過,就不要簡單認為我是苦逼工薪族。畢竟我也是身價上千萬,只是偶爾騎個車鍛鍊鍛鍊。(身價上千萬,昨晚夢到的。)

所以,從物件角度看,一個物件是否有狀態的充分必要條件是:

  1. 物件已例項化(處於執行時)
  2. 擁有非共享的狀態屬性
  3. 狀態持久化

那問題來了,我們經常寫的類建立的例項,是有狀態的嗎?

2. 應用

基於上面的總結,我們再來從應用的角度來看分析這個問題。

那應用的狀態和行為是什麼?
首先,只有執行中的應用才有狀態和行為。基於這個前提,個人理解執行時應用的狀態是應用持有的資料,行為是應用提供的功能。那應用的有無/無狀態界定就要看執行時應用持有的資料能否持久化。

以簡單的Web分層應用舉例 。從邏輯架構上來講應用一般分為三層,表示層、業務層和資料訪問層。上層進行狀態行為的封裝,資料層提供資料的持久化。所以從整體的角度來看,其是一個有狀態的應用。但單獨來看,我們不能對每一層進行有/無狀態的界定。第一,每一層不能單獨執行;第二,分層的目的是為了職責的隔離,每一層負責相應職責的抽象和封裝,其輸出的是類檔案,是物件的集合,沒有生命力。

那從物理架構上來講,Web應用可以分開兩個部分進行部署:Web例項和MySQL例項。也就是說應用和資料庫是可以分開部署的。這個時候Web例項就是無狀態的。那我們一般常說的無狀態服務其實是就是從這個拆分的角度來說的。

Actor 模型

理清完服務拆分的核心問題後,我們不得不來處理第二個棘手的問題:如何解決雲生應用高併發的應用場景呢?
那首先我們需要明確處理高併發的難點在哪?第一個是高效能,第二個就是:資源競爭導致的資料一致性問題。對於第一個難點,通過水平擴充套件服務可以化解;對於第二個難點,一般就是採用鎖機制,而對於雲生分散式的應用場景下,處理手段就更加複雜,可能需要使用分散式鎖,而這種做法,大大降低了應用的整體響應效能。那有沒有更好的解決方案,既兼顧效能又可以確保資料一致性呢?

有,藉助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中核心優勢:開發效率高、透明可伸縮。

開發效率高具體表現為:

  1. 物件導向的程式設計正規化去實現Grain
  2. Grain單執行緒執行
  3. Grain透明例項化:換句話說,應用無需關注Actor例項的建立、銷燬,可以直接呼叫Actor提供的方法。Actor的生命週期由Virtual Actor 執行時進行管理,類似GC,可以把Actor理解為完全託管的狀態。
  4. Grain位置透明:Actor之間通過持有彼此的邏輯引用(非例項引用)進行相互呼叫,而不需要知道Actor所處的實際位置。
  5. Grain狀態透明儲存
  6. 異常的自動傳播

透明可伸縮體現為:

  1. 應用狀態的隱式細粒度劃分
  2. 自適應的資源管理:Grain的生命週期完全由Orleans 執行時託管。
  3. 多路通訊:Grain的位置透明,Grain之間通過一組固定的TCP連結進行多路複用來進行訊息傳遞。
  4. 高效排程
  5. 顯式非同步

最後

這篇文章,就簡單寫到這裡,對於Orleans的詳細介紹後續會結合實際專案輸出更系統的應用細節,下次再見。

相關文章