關于如何設計一個基于事件驅動架構的思考
最近一直在思考一個問題:有沒有這樣一種可能,就是一個領域模型的狀態不依賴于外部,它只負責接收外部的事件,然后根據這些事件做出響應;響應分兩種:
1)根據模型當前的內存狀態進行業務邏輯處理,然后產生事件,注意:這個過程不會改變模型當前的內存狀態;
2)根據事件改變自己的狀態;
另外,也是最重要的,領域模型不用關心自己所產生的事件到底怎么樣了,比如不關心有沒有持久化,不關心是否和別的事件有并發沖突。它只管根據自己當前的內存狀態做上面這兩點的響應;
如果這樣的設想有可能,那領域模型就是真正的中央業務邏輯處理器了,和CPU很類似了。這樣它才能真正快起來。
簡單的說就是:事件->模型->事件
模型只管響應事件,然后產生新的事件
領域模型就是一黑盒,它只能幫你處理業務邏輯,其他的什么處理結果它一概不關心;當然,領域模型肯定有它自己的狀態,但這個狀態是駐留在內存的,和領域模型是一體的。
我為什么會有這個想法是因為,我在想,為什么要讓領域模型的處理邏輯依賴于它的處理結果是否被正確順利持久化了?感覺這很荒唐。
既然領域模型有自己的內存狀態空間,他的所有邏輯也應該只依賴于這個狀態空間,不再依賴于其他任何外部的東西。
當然,以前我們設計的IRepository,實際背后都是直接從數據庫取。這樣的話,領域模型的狀態空間就是數據庫了。但是這樣其實很不好,因為為什么不用內存作為領域模型的狀態空間呢?
現在再想想LMAX就是我剛才的想法的一個實際例子。
事件->模型->事件,這樣的設計,理論上并不需要必須要求單線程來訪問模型,因為領域模型不依賴于任何外部的狀態,只依賴于自己所在存活內存空間;單線程有一個很大的好處就是可以防止并發沖突的產生。我們其實完全支持多線程或集群的方式,只不過這樣會有可能訪問到的領域對象的狀態是了老的,因為不同的機器之間的領域模型內存對象的狀態需要做一些同步,訪問到老數據的可能性的大小取決于并發的大小以及機器之間數據同步的快慢;
LMAX之所以用單線程,是考慮了,這單線程的領域模型和性能之間,性能已經可以達到他們的要求了。
這樣的架構,領域模型中的任何一個對象的一次狀態更新至少會響應兩個事件,舉個例子:
1)先響應ChangeNoteCommand(command也是一種事件,可以理解為NoteChangeRequested),然后Note模型產生一個NoteChanged事件;
2)然后該事件(NoteChanged)最終又被發送到領域模型讓其響應,此時,領域模型才去更改自己的Note狀態;
經過這兩個事件的響應,才完成了Note的最終狀態的修改;而我們以前都是從數據庫取Note,然后更改,然后保存到數據庫。這樣不慢才怪!
通過上面的兩次事件響應,可以換來領域模型***的吞吐量。剩下的我們只要考慮:消息的序列化和反序列化、消息傳遞的速度、事件持久化的速度、并發沖突后重試的設計,以及消息丟失,等問題。但這些都不是領域模型該考慮的問題。這些外圍的任何問題,都不要讓領域模型自己去考慮,我們應該對出現的各種問題逐個尋求解決方案。
每個問題的解決方案我大概理了下我的對策:
- 消息的序列化和反序列化:這個簡單,用BinaryFormatter,或更快的開源序列化組件,可以達到每秒10W次每秒;
- 消息傳遞的速度:用MSMQ,如果嫌太慢,就用ZeroMq,可以達到30W消息每秒;
- 事件持久化的速度:由于事件都是跟著單個聚合根,所以我們只要確保單個聚合根的事件不會沖突(即沒有相同的版本號的事件);為了更快的持久化,我們可以對事件按照聚合根或者其他方式進行分區存放,不同的服務器存放不同的聚合根;這樣通過集群持久化的方式可以實現多事件同時被持久化,從而提高整體的事件持久化吞吐量;如單個mongodb server每秒持久化5000個,那10個mongodb server能每秒持久化5W個;
- 并發沖突后怎么辦:一般來說就是選擇重試,但為了確保不會出現不可控的局面(可能由于某種原因一直在重試),那需要設置一個***的重試次數;超過***重試次數后不再重試,然后記錄日志,以供以后查找問題;這里的重試的意思是:重新找到對應該事件的command,然后再次發送該command給領域模型處理;
- 消息丟失:丟失就丟失了唄,呵呵;要是你覺得消息決不能丟失,那就用可靠的帶持久化功能的消息傳輸隊列,如MSMQ;當然,就算消息丟失了,我們很多時候都要想想有沒有影響的,一般來說,消息丟失,至少我們是知道程序有問題了的,因為模型的狀態此時一定是不對的。我們可以通過在消息發出時和接收時記錄日志,這樣方便以后查找消息是在哪個環節丟的;
- 任何其他的異常出現,這個我覺得如果都是托管代碼,那可以在必要的地方加try catch,然后記錄日志。至于是否要重試,還要看情形;
另外,如果是多線程訪問模型,或集群訪問,那很多時候訪問到的內存的領域對象的狀態都是老的,那怎么辦?其實這不是問題,因為事件持久化的時候會被檢測到這種并發重復,然后對應的command會被重試。
另外,這種架構,傳輸的是事件,事件都是很小的,所以不用擔心消息傳輸的性能。
目前就想到這些。后續再完善思路,
***,我一直認為:知識決定命運,學習積累知識,而正確的思維方式是一切高效學習的基礎。所以要學會如何清晰地思考!
所以,我們最重要的是要學會如何思考。
呵呵!
原文鏈接:http://www.cnblogs.com/netfocus/archive/2013/03/26/2982152.html
































