CQRS和讀/寫分離
什麼是CQRS
CQRS(命令查詢響應性)是將數據模型和負責寫入(命令)和讀取(查詢)的組件分開的體系結構方法。
想法:狀態變化過程針對有效不變量和事務進行優化,而讀取則針對快速,有針對性的投影和縮放進行優化。
為什麼需要它
讀取性能:針對特定腳本(磁帶、報告、目錄)的實例化投影。
關鍵路徑的穩定性:記錄與「重」joins和聚集體隔離。
存儲選擇自由:用於寫入的OLTP,用於讀取的OLAP/kesh/搜索引擎。
加速演變:添加新的視圖而不帶有「中斷」交易的風險。
可觀察性和審核性(特別是與事件源結合使用):更容易恢復和重播狀態。
何時應用(以及何時不應用)
適合以下情況:- 具有不同數據切片和復雜聚合的讀數占主導地位。
- 關鍵的記錄路徑必須是微妙和可預測的。
- 需要不同的SLO/SLA進行讀寫。
- 需要將域寫邏輯與分析/搜索需求隔離。
- 域簡單,負載低;CRUD正在應對。
- 讀寫之間的強一致性對於所有腳本都是強制性的。
- 團隊經驗不足,操作復雜性不可接受。
基本概念
命令(Command):更改狀態的意圖(「CreateOrder」,「CapturePayment」)。檢查不變量。
查詢(Query)-獲取數據(「GetOrderById」,「ListUserTransactions」)。沒有副作用。
記錄模型:聚合/不變量/交易;存儲-關系/密鑰值/事件日誌。
讀取模型(投影):實例化表/索引/kesh,異步同步。
一致性:記錄和閱讀之間經常發生事件;關鍵路徑-通過直接讀取write模型。
建築(骨架)
1.寫服務:接受命令,驗證不變量,捕獲更改(DB或事件)。
2.Outbox/CDC:保證發布更改事實。
3.投影處理器:收聽事件/CDC並更新讀取模型。
4.閱讀服務:從實例化視圖/腰果/搜索服務查詢。
5.傳奇/編排:協調交叉聚合過程。
6.可觀察性:投影變量,成功應用百分比,DLQ。
記錄模型設計
聚合:明確的事務邊界(例如,「訂單」、「付款」、「用戶平衡」等)。
不變量:形式化(現金和≥ 0,唯一性,限制)。
指令在鍵上相等(例如「idempotency_key」)。
交易範圍很小;外部副作用-通過outbox。
命令示例(偽JSON)
json
{
"command": "CapturePayment",
"payment_id": "pay_123",
"amount": 1000,
"currency": "EUR",
"idempotency_key": "k-789",
"trace_id": "t-abc"
}
閱讀模型設計
推回查詢: 需要哪些屏幕/報告?
非歸一化是有效的:讀模型是「優化緩存」。
用於不同任務的多個投影:搜索(OpenSearch)、報告(列存儲)、卡(KV/Redis)。
TTL和重組:投影必須能夠從源中恢復(事件/快門反射)。
一致性和UX
Eventual consistency:接口可以短暫顯示舊數據。
UX模式:「數據更新」……,optimistic UI,同步指示器,在確認之前阻止危險活動。
對於需要強一致性的操作(例如,在註銷之前顯示準確的余額),直接從寫入模型中讀取。
CQRS和Event Sourcing(可選)
Event Sourcing (ES)存儲事件,而聚合狀態是其卷積的結果。
CQRS+ES捆綁提供了理想的審計和輕松的投影重組,但增加了復雜性。
備選方案:常規的OLTP-BD+outbox/CDC →投影。
復制: Outbox和CDC
Outbox(在一個事務中):記錄域更改+在outbox中記錄事件;Publischer送進輪胎。
CDC:從數據庫讀取(Debezium等)→轉換為域事件。
保修:默認情況下,消費者和投影必須相等。
選擇存儲
Write:事務關系(PostgreSQL/MySQL);KV/Document-其中不變量是簡單的。
Read:
KV/Redis-卡片和快速關鍵閱讀;
搜索(OpenSearch/Elasticsearch)-搜索/過濾器/面板;
專欄(ClickHouse/BigQuery)-報告;
CDN上的Kesh是公共目錄/內容。
集成模式
API層:「命令」和「查詢」的單獨端口/服務。
相似性:標題/身體中的操作鍵;使用TTL存儲recent-keys。
傳奇/編排:超時,補償,步驟的可重復性。
Backpressure:限制投影處理器的並行性。
可觀察性
write度量標準:p95/99指令潛伏期、成功交易比例、驗證錯誤。
讀取度量標準:p95/99查詢,點擊率,搜索群集負載。
投影變量(時間和消息)、DLQ率、重復數據消除百分比。
Tracing: 「trace_id」通過命令→ outbox →投影→查詢。
安全和合規性
分享權利:不同的記錄/寫作和閱讀角色;最小特權原則。
PII/PCI:在投影中最小化;at-rest/in-flight加密;偽裝。
審計:記錄命令,演員,結果,「trace_id」;用於關鍵域的WORM歸檔(付款,KYC)。
測試
合同測試:用於命令(錯誤,不變量)和queries(格式/過濾器)。
Projection tests: 提交一系列事件/CDC並驗證最終的閱讀模型。
混亂/後退:將延遲註入投影處理器;在滯後時檢查UX。
Replayability:從snapshot/loga重新組合展位上的投影。
移民與進化
新字段-在事件/CDC中加法;讀取模型重新組合。
方案重新設計下的雙重記錄(雙寫作);舊投影一直保持到切換。
轉化:「v1」/「v2」事件和尾聲,日落計劃。
Feature flags:在金絲雀上輸入新的查詢/投影。
反模式
CQRS在簡單的CRUD服務中「為了時尚」。
嚴格的同步讀取寫入依賴性(殺死隔離和穩定性)。
一個索引:將異構查詢混合到一個讀取商店中。
投影沒有等溫性→雙倍和差異。
不可修復的投影(沒有反射/狙擊)。
域示例
付款(在線服務)
寫作:交易數據庫中的「Authorize」,「Capture」,「Refund」;outbox發布「付費」。
Read:
Redis UI的「付款卡」;
報告的ClickHouse;
OpenSearch用於搜索事務。
關鍵途徑:授權≤ 800 ms p95;UI-eventual的讀取一致性(最多2-3 c)。
KYC
寫作:起步/升級狀態的團隊;將PII存儲在受保護的DB中。
閱讀:無PII狀態的輕量級投影;如果需要,PII可以點拉。
安全性:不同的狀態讀取和文檔訪問範圍。
資產負債表(iGaming/財務)
寫作:具有原子增量/減排量的「UserBalance」聚合體;手術的等效鍵。
閱讀:kesh for「快速平衡」;註銷-直接從write讀取(嚴格一致性)。
傳奇:存款/結論由事件協調,失敗時由補償協調。
實施支票清單
- 選擇了write模型的集合和不變量。
- 確定了關鍵的查詢並設計了下面的投影。
- 定制outbox/CDC和等效投影處理器。
- 有重新組合投影的計劃(snapshot/replay)。
- SLO:指令的潛在性,投影的脫落,可讀性/寫入。
- 分開訪問權限和實現數據加密。
- Alerta on DLQ/lag/重復數據消除失敗。
- 測試:合同,投影,混亂,倒帶。
FAQ
Event Sourcing是否必須用於CQRS?
沒有。您可以在常規DB+outbox/CDC上構建。
如何對抗同步?
明確設計UX,測量投影的時差,給關鍵操作從寫入讀取。
一個服務可以同時保留寫作和閱讀嗎?
是的,物理分離-可選;合乎邏輯的責任劃分是強制性的。
單位之間的交易呢?
通過傳奇和事件;如果可以的話,避免分布式事務。
結果
CQRS釋放手臂:具有清晰不變性和快速、目標讀取實例化投影的微妙、可靠的寫入路徑。這提高了性能,簡化了進化,並使系統對負載具有抵抗力-如果有紀律地管理一致性,可觀察性和遷移。