在現代系統中,特別是互聯網軟件,通常會涉及到大量用戶的並發訪問,我們的系統一定要在架構上支持高性能、大並發的訪問。一個高性能的系統通常由很多的方面組成,包括數據庫高性能、Web服務器高性能、負載均衡、緩存、軟件架構等。我們這篇文章先從軟件開發架構的角度作為切入點來介紹如何構建高性能的系統。
傳統架構性能的問題
我們先來看看DDD經典架構中,在多用戶、大並發訪問的情況下,對性能產生不利影響的因素。先來看看簡單架構圖:
1.通常會在當前界限上下文中只有一個領域模型,這個領域模型既會用於領域邏輯,同時也會用於持久化。
2.領域模型既會用於用例、也會用於查詢。
3.當前界限上下文通常只會有一個WebApi項目,這個項目中不同的Action Api用於不同的功能,有查詢的,有用例的。
從上面幾點大家可以看出,有以下幾個原因帶來性能的瓶頸:
1.上下文的查詢和用例通過一個模型來做,對應到數據庫來講,就是增、刪、改、查針對同一個數據庫的相關表,通過阻塞會造成性能問題;雖然通過建立好的索引可以進行緩解,但解決問題不徹底。
2.領域模型通常是根據業務需要進行設計的,也就是用於用例。如果用於查詢需求的話,可能會連接多個業務表才能完成查詢,查詢性能出現問題。
3.通常經典DDD是完成領域邏輯后,通過應用服務協調領域邏輯與倉儲來將領域對象持久化到數據存儲中,然后通過WebApi返回用戶結果。如果領域復雜,用戶並發量大的話,這個過程反饋到前端有一定的時間,用戶體驗不好。
4.當前界限上下文是一個WebApi項目,無論是用例還是查詢;這樣也無法對性能進行擴展,比如用例的在一些主機上,查詢的在另一些主機上。
為了有效的解決上述出現的性能問題,業界總結了一種架構風格,也就是CQRS(命令查詢職責分離)。通過CQRS的理念,可以有效的提高系統對大並發的支持。
命令指的是要更改對象狀態的行為,對系統有副作用;查詢指的是不更改對象狀態的行為,對系統無副作用。其實CQRS不僅僅用於大並發的處理,在日常開發中,其實也是可以利用這種理念的。
命令與查詢混在一起的情況:
private int Add(int a,int b) { int result = a + b; return result; }
從上面代碼可以看出,更改狀態與查詢是混在一起的;我們可以改造成命令與查詢分離的方式:
private int result; private void Add(int a,int b) { result = a + b; } private int QueryResult() { return result; }
基於上述代碼的思路與經典DDD架構的問題,我們就引入CQRS架構風格來解決性能問題。
CQRS架構
我們先來看看CQRS整體的架構圖,然后再說它是怎么解決性能問題的。
我們來看看整體架構圖的流程以及它是如何解決性能問題的:
1.首先可以看到命令與查詢走不同的WebApi服務,這樣可以將更改系統狀態的行為與查詢的行為做很好的隔離,並可以部署微服務到不同的服務器上,當然還可以通過NLB做進一步的擴展。
2.命令端的WebApi並不直接處理調用用例完成,而是接收到用戶命令時,將命令消息發布到消息總線,然后立刻返回一個操作信息給用戶,這樣用戶體驗很好,不需要等待業務邏輯完成與持久化完成。
3.命令處理器WebApi從消息隊列偵聽到消息,然后進行處理,處理的主要內容是完成領域邏輯調用,直接添加事件數據到事件存儲中。這里需要注意的是,並不是持久化到業務數據庫中。首先完成領域邏輯調用,可以得到用例最終正確的領域對象,然后存儲事件時,存儲這次領域對象的狀態,並且是直接添加。這樣做的好處有:一是加快持久化,二是能夠保存領域對象每次變化的信息,未來可以用於歷史追蹤、事件溯源與最終一致性。
4.命令處理器將領域對象發送到消息總線中,事件處理器會偵聽隊列,並最終將消息信息涉及到的領域對象持久化到業務數據庫中。
5.在查詢WebApi中,可以直接查詢業務庫,如果業務庫並不適合多表連接查詢時,可以再單獨做個拉平的為查詢提供服務的查詢庫。查詢庫的內容可以通過業務庫更新成功后,發布消息到另一個隊列中,然后通過處理器來處理這些數據到查詢庫中。
QQ討論群:309287205
微服務實戰視頻請關注微信公眾號: