Akka 系列(八):Akka persistence 設計理念之 CQRS

這一篇文章主要是講解Akka persistence的核心設計理念,也是CQRS(Command Query Responsibility Segregation)架構設計的典型應用,就讓咱們來看看爲何Akka persistence會採用CQRS架構設計。數據庫

CQRS

不少時候咱們在處理高併發的業務需求的時候,每每能把應用層的代碼優化的很好,好比緩存,限流,均衡負載等,可是很難避免的一個問題就是數據的持久化,以至數據庫的性能極可能就是系統性能的瓶頸,我前面的那篇文章也講到,若是咱們用數據庫去保證記錄的CRUD,在併發高的狀況下,讓數據庫執行這麼多的事務操做,會讓不少數據庫操做超時,鏈接池不夠用的狀況,致使大量請求失敗,系統的錯誤率上升和負載性能降低。緩存

既然這樣,那咱們可不可借鑑一下讀寫分離的思想呢?假使寫操做和同操做分離,甚至是對不一樣數據表,數據庫操做,那麼咱們就能夠大大下降數據庫的瓶頸,使整個系統的性能大大提高。那麼CQRS究竟是作了什麼呢?服務器

咱們先來看看普通的方式:架構


acid
acid

咱們能夠看出,咱們對數據的請求都是經過相應的接口直接對數據庫進行操做,這在併發大的時候確定會對數據庫形成很大的壓力,雖然架構簡單,但在面對併發高的狀況下力不從心。併發

那麼CQRS的方式有什麼不一樣呢?咱們也來看看它的執行方式:異步


acid
acid

乍得一看,彷佛跟普通的方式沒什麼不一樣啊,不就多了一個事件和存儲DB麼,其實否則,小小的改動即是核心理念的轉換,首先咱們能夠看到在CQRS架構中會多出一個Event,那它到底表明着什麼含義呢?其實看過上篇文章的同窗很容易理解,Event是咱們系統根據請求處理得出的一個領域模型,好比一個修改餘額操做事件,固然這個Event中只會保存關鍵性的數據。分佈式

不少同窗又有疑問了,這不跟普通的讀寫分離很像麼,難道還隱藏着什麼祕密?那咱們就來比較一下幾種方式的不一樣之處:高併發

1.單數據庫模式
  • 寫操做會產生互斥鎖,致使性能下降;
  • 即便使用樂觀鎖,可是在大量寫操做的狀況下也會大量失敗;
2.讀寫分離
  • 讀寫分離經過物理服務器增長,負荷增長;
  • 讀寫分離更適用於讀操做大於寫操做的場景;
  • 讀寫分離在面對大量寫操做的狀況下仍是很吃力;
3.CQRS
  • 普通數據的持久化和Event持久化可使用同一臺數據庫;
  • 利用架構設計可使讀和寫操做盡量的分離;
  • 能支撐大量寫的操做狀況;
  • 能夠支持數據異步持久,確保數據最終一致性;

從三種方式各自的特色能夠看出,單數據庫模式的在大量讀寫的狀況下有很大的性能瓶頸,但簡單的讀寫分離在面對大量寫操做的時候也仍是力不從心,好比最多見的庫存修改查詢場景:性能


common-action
common-action

咱們能夠發如今這種模式下寫數據庫的壓力還會很大,並且還有數據同步,數據延遲等問題。優化

那麼咱們用CQRS架構設計會是怎麼樣呢:


cqrs-action
cqrs-action

首先咱們能夠業務模型進行分離,對不一樣的查詢進行分離,另外避免不了的同一區間數據段進行異步持久化,在保證數據一致性的狀況下提高系統的吞吐量。這種設計咱們不多會遇到事務競爭,另外還可使用內存數據庫(固然若是是內存操做那就最快)來提高數據的寫入。(以上的數據庫均可爲分佈式數據庫,不擔憂單機宕機)

那麼CRQS機制是怎麼保證數據的一致性的呢?

從上圖中咱們能夠看出,一個寫操做咱們會在系統進行初步處理後生成一個領域事件,好比a用戶購買了xx商品1件,b用戶購買了xx商品2件等,按照普通的方式咱們確定是直接將訂單操做,庫存修改操做一併放在一個事務內去操做數據庫,性能可想而知,而用CQRS的方式後,首先系統在持久化相應的領域事件後和修改內存中的庫存(這個處理很是迅速)後即可立刻向用戶作出反應,真正的具體信息持久能夠異步進行,固然如果當在具體信息持久化的過程當中出錯了怎麼辦,系統能恢復正確的數據麼,固然能夠,由於咱們的領域事件事件已經持久化成功了,在系統恢復的時候,咱們能夠根據領域事件來恢復真正的數據,固然爲了防止恢復數據是形成數據丟失,數據重複等問題咱們須要制定相應的原則,好比給領域事件分配相應id等。

使用CQRS會帶來性能上的提高,固然它也有它的弊端:

  • 使系統變得更復雜,作一些額外的設計;
  • CQRS保證的是最終一致性,有可能只適用於特定的業務場景;

Akka Persistence 中CQRS的應用

經過上面的講解,相信你們對CQRS已經有了必定的瞭解,下面咱們就來看看它在Akka Persistence中的具體應用,這裏我就結合上一篇文章抽獎的例子,好比其中的LotteryCmd即是一個寫操做命令,系統通過相應的處理後獲得相應的領域事件,好比其中LuckyEvent,而後咱們將LuckyEvent進行持久化,並修改內存中抽獎的餘額,返回相應的結果,這裏咱們就能夠同時將結果反饋給用戶,並對結果進行異步持久化,流程以下:


cqrs-example
cqrs-example

能夠看出,Akka Persistence的原理徹底是基於CQRS的架構設計的,另外Persistence Actor還會保存一個內存狀態,至關於一個in memory數據庫,能夠用來提供關鍵數據的存儲和查詢,好比前面說到的庫存,餘額等數據,這部分的設計取決於具體的業務場景。

閱讀Akka Persistence相關源碼,其的核心就在於PersistentActor接口中的幾個持久方法,好比其中的

def persist[A](event: A)(handler: AUnit): Unit

def persistAll[A](events: immutable.Seq[A])(handler: AUnit): Unit複製代碼

等方法,它們都有兩個參數,一個是持久化的事件,一個是持久化後的後續處理邏輯,咱們能夠在後續handler中修改Actor內部狀態,向外部發送消息等操做,這裏的模式就是基於CQRS架構的,修改狀態有事件驅動,另外Akka還能夠在系統出錯時,利用相應的事件恢復Actor的狀態。

總結

總的來講,CQRS架構是一種不一樣於以往的CRUD的架構,因此你在享受它帶來的高性能的同時可能會遇到一些奇怪的問題,固然這些都是能夠解決的,重要的是思惟上的改變,好比事件驅動,領域模型等概念,不過相信當你理解並掌握它以後,你便會愛上它的。

相關文章
相關標籤/搜索