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释放手臂:具有清晰不变性和快速、目标读取实例化投影的微妙、可靠的写入路径。这提高了性能,简化了进化,并使系统对负载具有抵抗力-如果有纪律地管理一致性,可观察性和迁移。