1. 背景
在传统架构中,系统通常使用 单一数据模型 处理读写:
- 写入:更新数据库中的一条记录
- 读取:查询同一张表返回数据
这种模式的问题是:
- 读写冲突:高并发场景下,读和写争抢数据库资源
- 难以扩展:同一数据模型既要支撑事务写入,又要支撑复杂查询
- 历史丢失:只保存最新状态,无法还原数据变化过程
为了解决这些问题,出现了 CQRS + Event Sourcing(事件溯源) 架构模式。
2. 什么是 CQRS?
CQRS 全称 Command Query Responsibility Segregation,即 命令与查询职责分离:
- 命令(Command) :修改系统状态(写操作)
- 查询(Query) :读取系统状态(读操作)
👉 在 CQRS 架构中,写模型和读模型分离:
- 写入模型保证业务逻辑和一致性
- 读取模型可以为性能和多样化查询优化(甚至用不同数据库)
3. 什么是事件溯源(Event Sourcing)?
事件溯源的核心思想是:
👉 系统状态不是直接存储,而是由一系列事件计算得出。
- 写入时:不是直接更新数据库,而是记录“事件”
- 查询时:通过事件重放或投影得到最新状态
例如:
传统模式:余额 = 100
事件溯源:存入 50 → 支出 30 → 存入 80 → 最终余额 = 100
这意味着:
- 可以回放事件,重建任意时刻的状态
- 业务更可追溯,可用于审计与合规
4. CQRS + 事件溯源如何结合?
两者常常搭配使用:
- 命令(Command) :业务逻辑执行后,生成事件(例如 “订单已创建”)
- 事件存储(Event Store) :事件持久化到事件日志(Kafka、EventStoreDB)
- 投影(Projection) :事件异步更新到查询数据库(ElasticSearch、Redis、MongoDB)
- 查询(Query) :读取优化后的查询模型,返回结果
这种模式让 写入模型与读取模型彻底解耦。
5. 优势
- 高扩展性:读写分离,可独立扩展查询服务
- 多样化存储:读模型可用 Redis、ES,写模型可用关系数据库
- 可追溯性:通过事件日志还原任意历史状态
- 天然支持审计:金融、风控场景尤其适用
6. 挑战
- 数据一致性:读模型异步更新,存在延迟
- 事件存储成本:事件数量庞大,需要快照机制优化
- 开发复杂度:开发者需要管理 Command、Event、Projection 三类逻辑
- 学习曲线高:团队需要新的思维方式
7. 应用场景
金融系统
- 转账、支付等操作需要完整的事件日志,方便审计与追溯。
电商订单
- “订单已创建 → 已支付 → 已发货 → 已完成”,天然符合事件溯源模型。
IoT & 日志平台
- 大量状态变化(传感器数据、操作日志)更适合存储为事件流。
游戏后端
- 玩家行为事件可重放,方便调试、回放和作弊检测。
8. 实现示例
写入(Command → Event)
# 示例:订单服务
class OrderService:
def create_order(self, order_id, user_id, items):
event = {
"type": "OrderCreated",
"order_id": order_id,
"user_id": user_id,
"items": items
}
event_store.append(event)
投影(Event → Read Model)
# 示例:订单投影
def handle_order_created(event):
read_db["orders"][event["order_id"]] = {
"status": "CREATED",
"user_id": event["user_id"],
"items": event["items"]
}
查询时,直接访问 read_db,不影响写入性能。
9. 总结
CQRS + 事件溯源让后端具备:
- 写读解耦,高性能扩展
- 事件驱动,可追溯历史
- 更适合复杂业务(金融、电商、IoT)
但代价是:
- 架构复杂度提高
- 一致性变成最终一致
- 对团队能力要求更高
👉 一句话总结:
CQRS + Event Sourcing 是复杂系统的“重武器”,适合规模大、合规要求高的后端系统。