CQRS命令查詢分離架構的多種形式實現 - Kapil

banq發表於2021-11-19

CQRS(命令查詢職責分離)的核心有一個簡單的目標:將讀取和寫入分離為單獨的模型。這個簡單的想法可以採用多種形式,具體取決於使用它的上下文以及所使用的實現選擇。這篇文章試圖分析 CQRS 的各種形狀,以及所有這些形狀如何支援解耦讀寫的中心思想。
世界變得複雜了。資訊系統不再只是簡單的 CRUD 風格的應用程式,並沒有適用於所有型別操作的規範資料模型!
雲、微服務、流媒體、反應式系統意味著現在需要以不同的方式考慮這種多樣化、分散式和始終動態的資料來源和資料消費。
CQRS 是這種思維過程的結果之一。出於記錄和讀取目的而以不同方式檢視資料,並承認這兩種模型不需要相同。這種想法一旦被接受,它就會開啟很多選項:
  • · 資料模型
  • · 資料儲存
  • · 一致性選項
  • · 應用設計
  • · 執行時環境
  • · 訪問模式(例如,大量讀取,少量寫入)

請注意,在 CQRS 一詞被創造之前,它的原理透過資料倉儲和分析產品的報告資料庫仍在使用,這在某種程度上遵循了 CQRS 的核心主題,將讀取和查詢解耦。這些產品通常使用某種 ETL 或資料庫複製過程從資料庫中獲取資料,並用作資訊系統。雖然這些技術在一個層面上遵循 CQRS,但這個術語現在通常是指單個程式(例如,模組或微服務)為不同型別的使用者請求採用讀寫模型。因此,它更像是一種 OLTP 模式,而不是以前的 OLAP 模式。
事實上,擁有分析系統和資料湖等可能是 CQRS 的因果優勢,但這些都不是根本原因。
最後值得一提的一點是,CQRS 在領域驅動設計成為話題之後開始流行起來。命令和查詢指的是 DDD 中的域事件,因此它不是簡單的 WriteReadResponsibilitySeggretaion,而是隨著這些術語的 DDD 版本而廣為人知。
下一節嘗試分析可用於實現 CQRS 的不同選項以及這些選項如何影響以下特性:資料模型、一致性、資料庫選擇、傳輸模型。這些選項的順序沒有意義,但可能它們從簡單到相對複雜。
  

以應用為中心的 CQRS
CQRS 的簡單實現形式,旨在建立不同的讀寫服務和模型,以在這些操作之間提供清晰的模組子邊界。命令服務在資料庫中執行寫入。查詢服務從同一個資料庫中讀取資料,但會生成不同的查詢模型,例如用於在 UI 中顯示。查詢模型可以選擇對這些資料進行轉換和投影。
底層資料庫保持不變,因此 DB 約束和模式在兩者中仍然有效。
由於底層資料庫相同,DB一致性強(*)。
  

資料庫只讀副本
只讀副本提供不同的讀取資料庫,其中包含主寫入資料庫的只讀副本。
由於資料庫結構與寫入資料庫完全相同,因此服務可以在程式碼中使用不同的查詢模型。
此模式用於從查詢中解除安裝寫入資料庫,以平衡一個或多個讀取例項之間的查詢流量。
複製工具通常由IBM DB2Oracle等 DB 供應商提供。較新的雲原生託管資料庫提供只讀副本作為託管服務,並負責只讀副本的複製和高可用性。
在特殊情況下,資料庫複製也可以調整為強一致性(又名複製因子)。在這種情況下,複製是為了支援強一致性模型(CAP 中的 C)。例如,AWS S3 提供了強大的先寫後讀一致性模型。Cassandra 提供了可配置的讀取一致性級別設定。
  

事件釋出
許多事件驅動模式補充了 CQRS 風格的應用程式。命令服務寫入命令資料庫。因此,任何改變域狀態的域事件都是一個命令,並被寫入最佳化了寫入的資料庫。對於先讀後寫的情況,也可以事務性地讀取命令資料庫,但這通常是在聚合的小上下文中。此外,命令服務還將命令釋出到訊息傳遞系統和具有規範事件結構的命令事件中。
任何服務都可以訂閱此事件,在這種情況下,查詢服務訂閱會偵聽該事件並構建針對讀取進行最佳化的本地資料庫。與 DB 複製相反,這種方法提供了以任何讀取最佳化方式投射事件的自由。事實上,事件底層資料庫的選擇可以與命令資料庫不同,從而實現多語言持久化。
要點:

  • 1. 查詢端會看到最終一致的資料,因此需要在應用程式設計和使用者體驗中考慮到這一點。對於要用於寫入命令 DB 中的資料,不應讀取查詢 DB。
  • 2.命令服務需要以某種方式管理資料庫寫入和事件釋出事務(都可以或都回滾)。這可能意味著 XA 或 2PC 方法可能很脆弱。
  • 3. 在失敗的情況下,事件可以透過多次重試來傳遞,因此可以多次到達訂閱者(稱為“至少一次”傳遞)。訂閱者需要是冪等的並且只處理一次事件。訊息系統增加了 DR 元件,例如持久佇列或死信佇列,以在訂閱者或訊息系統出現故障時保留訊息。
  • 4. 根據命令服務的執行時間釋出事件,因此沒有順序保證。這意味著在 T1 發生的域事件 D1 可以晚於在 T2 發出的另一個域事件 D2 到達訂閱者。此外,在訂閱者失敗的情況下,當訂閱者再次健康時,事件可能會亂序重新傳遞。必須解決亂序交付問題(尤其是在 JMS 和 AMQP 風格的訊息傳遞中,而在 Kafka 或 Akka & Kinesis Streams 等事件流系統中,消費者可以控制他們為某個時間消耗的訊息偏移量劃分)。通常,這是透過將事件時間戳作為訊息屬性來解決的,該屬性不同於訊息中介軟體在向其釋出事件時建立的時間戳標頭。

 

4. CDC(變更資料捕獲)
CDC 將命令資料庫作為命令事件的來源。命令服務只需要事務性地提交到事務資料庫,然後資料庫事務日誌成為事件觸發器,免除了命令服務來管理資料庫寫入和事務性事件。
這裡引入了額外的日誌抓取元件,它通常是一個資料庫原生元件,並“掛鉤”到事件儲存以釋出時間順序事件。
要點:

  • 1. 參考前面“事件釋出” #1
  • 2. 參考前面“事件釋出” #3。此外,由於命令資料庫事務日誌仍然具有所有更改,因此它提供了另一層以在失敗時重試。
  • 3. 資料庫事務日誌是有序的,保證了訊息的排序。

資料庫供應商為流行的資料庫提供了一個日誌抓取系統。這些與 CDC 工具結合使用以啟用源和目標聯結器。兩個流行的選擇是DebeziumKafka Connect。Kafka Connect 提供對大量“源”和“接收”聯結器的支援。
雲資料庫將此模式作為託管的雲原生服務提供。例如AWS Dynamo Streams(由AWS Kinesis Data Stream支援)。
這種模式也在微服務事務發件箱模式事務日誌拖尾模式中捕獲。
AxonEventuate這樣的框架為這種模式提供了支援。
  

5. 事件溯源和 CQRS
事件溯源是一種將應用程式設計為一系列領域事件的方法,這些事件以時間順序儲存在事件儲存中。事件儲存可以是一個事件平臺,例如 Kafka 或 AWS Kinesis,也可以只是一個資料庫。Event Store 是關於狀態的“單一事實來源”。重要的一點是域沒有“當前狀態”。當前狀態是派生狀態,可以透過在聚合上重放事件來實現。事件溯源將更改的隱式審計日誌作為一組域事件提供。另一方面,由於沒有實體狀態,因此無法在命令事件儲存中查詢實體。為了克服這個問題,CQRS 通常被視為事件溯源的補充模式,使實體的派生狀態在單獨的查詢資料儲存中可用。
也可以使用 CDC 模式實現 CQRS。如果事件儲存是像 Kafka 這樣的訊息傳遞系統,事件儲存將成為查詢服務(以及任何其他服務,如分析、欺詐預防等)的訂閱,以獲取域命令事件流並建立讀取最佳化模型用於查詢。
如果命令服務需要讀取一些實體資料怎麼辦?理想情況下,它不應該依賴於先前的狀態,因為所有命令服務應該做的只是命令的追加日誌(事件儲存的選擇,即訊息傳遞與 DB 對這一點來說意義不大)。但是萬一呢?命令服務也可以讀取查詢儲存投影嗎?答案是根據上下文而定的,可能是可以的,但這裡有一個最終的一致性。只要命令處理不依賴於它需要讀取的資料的強一致性保證,它應該可以讀取。可能將其保留為邊緣情況是謹慎的。
框架: Lightbend Akka & Axon 
 
快取:讀層快取是一種通用選擇,在所有選項中都可用,而不是CQR的必然結果。
一致性:在這裡,資料一致性考慮是最重要的。只讀資料、命令端讀取和先讀後寫資料的情況需要不同的一致性保證。
 

相關文章