天天看點

分布式架構EventSourcing& CQRS

微服務将原來的N個子產品,或者說服務,按照适當的邊界,從單節點劃分成一整個分布式系統中的若幹節點上。

原來服務間的互動直接代碼級調用,現在則需要通過以下幾種方式調用:

  • SOA請求
  • RPC調用
  • ED(EventDriven)事件驅動

前面兩種就比較類似,都屬于直接調用,好處明顯,缺點是請求者必須知道被請求方的位址。現在一般會提供額外的機制,如服務注冊、發現等,來提供動态位址,實作負載和動态路由。目前大多數微服務架構都走的這條路子,如當下十分火熱的SpringCloud等。

事件驅動的方式,把請求者與被請求者的綁定關系解耦了,但是需要額外提供一個消息隊列,請求者直接把消息發送到隊列,被請求者監聽隊列,在擷取到與自己有關系的事件時進行處理。主要缺點主要有二:

1) 調用鍊不再直覺;

2) 高度依賴隊列本身的性能和可靠性;

但無論是哪種方式,都使得傳統架構下的事務無法再起到原先的作用了。

事務的作用主要有二:

  • 統一結果,要麼都成功,要麼都失敗
  • 并發時保證原子性操作

在傳統架構下,無論是DB還是架構所提供的事務操作,都是基于同線/程序的。在微服務所處的分布式架構下,業務操作變成跨程序、跨節點,隻能自行實作,而由于節點通信狀态的不确定性、節點間生命周期的不統一等,把實作分布式事務的難度提高了很多。

這就是微服務中的一個大難題。

什麼是EventSourcing?

不儲存對象的最新狀态,而是儲存對象産生的所有事件。

通過事件回溯(Event Sourcing, ES)得到對象最新的狀态

以前我們是在每次對象參與完一個業務動作後把對象的最新狀态持久化儲存到資料庫中,也就是說我們的資料庫中的資料是反映了對象的目前最新的狀态。而事件溯源則相反,不是儲存對象的最新狀态,而是儲存這個對象所經曆的每個事件,所有的由對象産生的事件會按照時間先後順序有序的存放在資料庫中。當我們需要這個對象的最新狀态時,隻要先建立一個空的對象,然後把和改對象相關的所有事件按照發生的先後順序從先到後全部應用一遍即可。這個過程就是事件回溯。

因為一個事件就是表示一個事實,事實是不能被磨滅或修改的,是以ES中的事件本身是不可修改的(Immutable),不會有DELETE或UPDATE操作。

ES很明顯先天就會有個問題——由于不停的記錄Event,回溯獲得對象最新狀态所需花的時間會與事件的數量成正比,當資料量大了以後,擷取最新狀态的時間也相對的比較長。

而在很多的邏輯操作中,進行“寫”前一般會需要“讀”來做校驗,是以ES架構的系統中一般會在記憶體中維護一份對象的最新狀态,在啟動時進行”預熱”,讀取所有持久化的事件進行回溯。這樣在讀對象——也就是

Aggregate

的最新狀态時,就不會因為慢影響性能。

同時,也可以根據一些政策,把一部分的Event合集所産生的狀态作為一個snapshot,下次直接從該snapshot開始回溯。

既然需要讀,就不可避免的遇到并發問題。

EventSourcing要求對回溯的操作必須是原子性的,具體實作可參照Actor模型。

C端的指令的執行流程

用戶端如(MVC Controller)發送指令通知系統做修改:

  1. 發送指令到分布式MQ;
  2. 然後指令的訂閱者處理指令;
  3. 訂閱者内部根據不同的指令調用不同的Command Handler進行處理;
  4. Command Handler内部根據指令所指定的聚合根ID從In-Memory記憶體中直接擷取聚合根對象的引用,然後操作聚合根對象;
  5. 聚合根對象狀态發生變化并産生事件;
  6. 架構負責自動持久化事件到Event Storage(簡稱EventStore);
  7. 架構負責将事件釋出到Event MQ;
  8. Event訂閱者訂閱事件,然後調用對應的Event Handler進行處理,如更新Data Storage(儲存了聚合根的最新狀态,通常叫讀庫,ReadDB);

Q端的查詢的執行流程

用戶端如(MVC Controller)發出查詢請求系統傳回資料:

  1. 調用輕薄的Query Service,傳如Query DTO;
  2. Query Service從讀庫進行查詢并傳回結果;

讀庫可以有很多種,依據我們的業務場景來選擇:比如關系型DB、分布式緩存等NoSQL、搜尋引擎,etc.

eventuate架構實作了CQRS&ES,正在研究過程中,以後會放上這個架構