🌀 CQRS + 事件溯源:后端高一致性架构的“双剑合璧”

97 阅读2分钟

1. 前言

在传统的后端架构中,读写操作共享同一套数据模型和存储结构。
这种方式简单直观,但在高并发、高一致性的场景下,很容易遇到以下问题:

  • 数据库压力大(写多读多)
  • 读写性能互相影响
  • 难以追踪数据变化的历史过程

CQRS(Command Query Responsibility Segregation,命令查询职责分离)和事件溯源(Event Sourcing)结合,可以同时解决性能与数据一致性问题,并且天然支持审计与回溯。


2. 核心概念

2.1 CQRS

  • Command(写模型) :处理变更数据的请求
  • Query(读模型) :处理读取数据的请求
  • 读写分离,互不影响

2.2 Event Sourcing

  • 数据不是直接存储当前状态,而是存储事件流(例如:“订单创建”、“订单支付”、“订单取消”)
  • 当前状态由事件流**重放(Replay)**生成

3. 架构优势

  1. 高性能读写分离

    • 读模型可单独优化(缓存、索引、搜索引擎)
  2. 天然审计能力

    • 每个数据变更都有完整事件记录
  3. 灵活扩展业务逻辑

    • 新功能只需订阅事件流,无需改原有业务
  4. 易于调试与回溯

    • 通过事件重放,恢复任意时刻的数据状态

4. 技术选型

功能技术方案
事件存储EventStoreDB、Kafka、PostgreSQL JSONB
读模型存储ElasticSearch、MongoDB、Redis
框架支持Axon Framework(Java)、Lagom(Scala)、NestJS CQRS 模块(Node.js)

5. 架构示例:订单系统

5.1 流程

  1. 用户创建订单(Command)
  2. 系统生成 OrderCreated 事件并存储
  3. 事件消费者更新读模型(订单视图)
  4. 前端查询读模型获取订单信息
[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,但在复杂业务下,它能极大提升系统的可维护性和可扩展性。