1. 前言
在传统的后端架构中,读写操作共享同一套数据模型和存储结构。
这种方式简单直观,但在高并发、高一致性的场景下,很容易遇到以下问题:
- 数据库压力大(写多读多)
- 读写性能互相影响
- 难以追踪数据变化的历史过程
CQRS(Command Query Responsibility Segregation,命令查询职责分离)和事件溯源(Event Sourcing)结合,可以同时解决性能与数据一致性问题,并且天然支持审计与回溯。
2. 核心概念
2.1 CQRS
- Command(写模型) :处理变更数据的请求
- Query(读模型) :处理读取数据的请求
- 读写分离,互不影响
2.2 Event Sourcing
- 数据不是直接存储当前状态,而是存储事件流(例如:“订单创建”、“订单支付”、“订单取消”)
- 当前状态由事件流**重放(Replay)**生成
3. 架构优势
-
高性能读写分离
- 读模型可单独优化(缓存、索引、搜索引擎)
-
天然审计能力
- 每个数据变更都有完整事件记录
-
灵活扩展业务逻辑
- 新功能只需订阅事件流,无需改原有业务
-
易于调试与回溯
- 通过事件重放,恢复任意时刻的数据状态
4. 技术选型
| 功能 | 技术方案 |
|---|---|
| 事件存储 | EventStoreDB、Kafka、PostgreSQL JSONB |
| 读模型存储 | ElasticSearch、MongoDB、Redis |
| 框架支持 | Axon Framework(Java)、Lagom(Scala)、NestJS CQRS 模块(Node.js) |
5. 架构示例:订单系统
5.1 流程
- 用户创建订单(Command)
- 系统生成
OrderCreated事件并存储 - 事件消费者更新读模型(订单视图)
- 前端查询读模型获取订单信息
[Command API] → [事件存储] → [事件处理器] → [读模型存储] → [Query API]
6. 代码示例(NestJS CQRS + Event Sourcing)
// 命令
export class CreateOrderCommand {
constructor(public readonly orderId: string, public readonly items: string[]) {}
}
// 事件
export class OrderCreatedEvent {
constructor(public readonly orderId: string, public readonly items: string[]) {}
}
// 命令处理器
@CommandHandler(CreateOrderCommand)
export class CreateOrderHandler implements ICommandHandler<CreateOrderCommand> {
async execute(command: CreateOrderCommand) {
this.eventBus.publish(new OrderCreatedEvent(command.orderId, command.items));
}
}
// 事件处理器(更新读模型)
@EventsHandler(OrderCreatedEvent)
export class OrderCreatedHandler implements IEventHandler<OrderCreatedEvent> {
async handle(event: OrderCreatedEvent) {
await this.orderReadRepository.save({ id: event.orderId, items: event.items });
}
}
7. 落地建议
- 幂等处理:防止重复事件造成数据错误
- 事件版本化:业务升级时保证老事件能被正确重放
- 最终一致性:读模型可能存在短暂延迟,需要业务容错
- 数据归档:长时间的事件流需要定期归档以控制存储成本
8. 总结
CQRS + 事件溯源的组合,非常适合需要高一致性、可回溯性、灵活扩展的后端系统。
虽然实现成本高于传统 CRUD,但在复杂业务下,它能极大提升系统的可维护性和可扩展性。