SpringBoot + 事件溯源 + CQRS:高一致性与高性能读写分离架构
一、你的系统是不是也遇到了这些问题?
公司的订单系统越来越慢了,用户查询订单要等好几秒,而且经常出现数据不一致的问题。更糟糕的是,每次优化查询性能,都会影响订单创建的性能;优化订单创建,查询又变慢了。简直是个死循环。
这样的场景,作为后端开发的你,是不是也遇到过?
二、传统架构的困境
在传统的CRUD架构中,我们通常是这样设计的:
- 一个数据库表存储订单的所有信息
- 写操作(创建、更新、删除)直接操作数据库
- 读操作(查询)也从同一个数据库读取
这种架构在业务简单、数据量不大的情况下工作得很好。但随着业务的发展,问题就暴露出来了:
1. 读写冲突
写操作和读操作都在同一个数据库上,相互影响。写操作需要加锁,影响读性能;读操作占用连接,影响写性能。
2. 性能瓶颈
随着数据量的增长,查询越来越慢。为了优化查询,我们添加索引、做分库分表,但这些都增加了系统的复杂度。
3. 数据一致性
在分布式环境下,保证数据一致性变得更加困难。多个服务同时操作同一份数据,很容易出现数据不一致的情况。
4. 审计困难
我们只知道数据的当前状态,但不知道数据是如何变成这样的。如果出现问题,很难追溯原因。
三、事件溯源 + CQRS:架构设计的革命
为了解决这些问题,业界提出了两个重要的架构模式:
1. 事件溯源(Event Sourcing)
传统的CRUD架构存储的是数据的当前状态,而事件溯源存储的是导致状态变化的事件。
打个比方:
- 传统架构:就像拍照片,只能看到当前的样子
- 事件溯源:就像录像带,可以回放整个过程
2. CQRS(Command Query Responsibility Segregation)
CQRS的核心思想是将**写操作(Command)和读操作(Query)**分离:
- 写操作:只负责接收命令,发布事件
- 读操作:只负责查询数据,从读模型中读取
四、方案详解
1. 事件溯源的实现
(1)事件定义
首先,我们需要定义事件:
// 订单创建事件
public class OrderCreatedEvent {
private String orderId;
private String userId;
private BigDecimal amount;
private List<OrderItem> items;
private LocalDateTime createTime;
}
// 订单支付事件
public class OrderPaidEvent {
private String orderId;
private String transactionId;
private LocalDateTime payTime;
}
// 订单取消事件
public class OrderCancelledEvent {
private String orderId;
private String reason;
private LocalDateTime cancelTime;
}
(2)事件存储
事件需要持久化存储,我们可以使用数据库来存储事件:
@Entity
@Table(name = "event_store")
public class EventStore {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "aggregate_id", nullable = false)
private String aggregateId; // 聚合根ID
@Column(name = "event_type", nullable = false)
private String eventType; // 事件类型
@Column(name = "event_data", columnDefinition = "JSON", nullable = false)
private String eventData; // 事件数据
@Column(name = "version", nullable = false)
private Long version; // 版本号
@Column(name = "create_time", nullable = false)
private LocalDateTime createTime;
}
(3)事件发布
当业务操作发生时,我们发布事件而不是直接修改数据:
@Service
public class OrderCommandService {
@Autowired
private EventStore eventStore;
@Autowired
private ApplicationEventPublisher eventPublisher;
public void createOrder(CreateOrderCommand command) {
// 1. 创建订单ID
String orderId = UUID.randomUUID().toString();
// 2. 创建订单创建事件
OrderCreatedEvent event = new OrderCreatedEvent();
event.setOrderId(orderId);
event.setUserId(command.getUserId());
event.setAmount(command.getAmount());
event.setItems(command.getItems());
event.setCreateTime(LocalDateTime.now());
// 3. 存储事件
eventStore.saveEvent(orderId, event);
// 4. 发布事件
eventPublisher.publishEvent(event);
}
}
2. CQRS的实现
(1)命令处理器
命令处理器负责处理写操作:
@Component
public class OrderCommandHandler {
@Autowired
private EventStore eventStore;
@Autowired
private ApplicationEventPublisher eventPublisher;
@Autowired
private OrderQueryService queryService;
public void handle(CreateOrderCommand command) {
// 验证订单
validateOrder(command);
// 创建订单创建事件
OrderCreatedEvent event = new OrderCreatedEvent();
event.setOrderId(command.getOrderId());
event.setUserId(command.getUserId());
event.setAmount(command.getAmount());
event.setItems(command.getItems());
event.setCreateTime(LocalDateTime.now());
// 存储事件
eventStore.saveEvent(command.getOrderId(), event);
// 发布事件
eventPublisher.publishEvent(event);
}
public void handle(PayOrderCommand command) {
// 验证订单状态
Order order = queryService.getOrder(command.getOrderId());
if (order.getStatus() != OrderStatus.PENDING) {
throw new RuntimeException("订单状态不正确");
}
// 创建订单支付事件
OrderPaidEvent event = new OrderPaidEvent();
event.setOrderId(command.getOrderId());
event.setTransactionId(command.getTransactionId());
event.setPayTime(LocalDateTime.now());
// 存储事件
eventStore.saveEvent(command.getOrderId(), event);
// 发布事件
eventPublisher.publishEvent(event);
}
}
(2)事件处理器
事件处理器监听事件,更新读模型:
@Component
@Slf4j
public class OrderEventHandler {
@Autowired
private OrderReadModelRepository readModelRepository;
@EventListener
public void handle(OrderCreatedEvent event) {
log.info("处理订单创建事件:{}", event.getOrderId());
// 创建读模型
OrderReadModel readModel = new OrderReadModel();
readModel.setOrderId(event.getOrderId());
readModel.setUserId(event.getUserId());
readModel.setAmount(event.getAmount());
readModel.setStatus(OrderStatus.PENDING);
readModel.setItems(event.getItems());
readModel.setCreateTime(event.getCreateTime());
// 保存读模型
readModelRepository.save(readModel);
}
@EventListener
public void handle(OrderPaidEvent event) {
log.info("处理订单支付事件:{}", event.getOrderId());
// 更新读模型
OrderReadModel readModel = readModelRepository.findByOrderId(event.getOrderId());
if (readModel != null) {
readModel.setStatus(OrderStatus.PAID);
readModel.setPayTime(event.getPayTime());
readModelRepository.save(readModel);
}
}
@EventListener
public void handle(OrderCancelledEvent event) {
log.info("处理订单取消事件:{}", event.getOrderId());
// 更新读模型
OrderReadModel readModel = readModelRepository.findByOrderId(event.getOrderId());
if (readModel != null) {
readModel.setStatus(OrderStatus.CANCELLED);
readModel.setCancelReason(event.getReason());
readModel.setCancelTime(event.getCancelTime());
readModelRepository.save(readModel);
}
}
}
(3)查询服务
查询服务只负责读操作:
@Service
public class OrderQueryService {
@Autowired
private OrderReadModelRepository readModelRepository;
public Order getOrder(String orderId) {
OrderReadModel readModel = readModelRepository.findByOrderId(orderId);
if (readModel == null) {
throw new RuntimeException("订单不存在");
}
return convertToOrder(readModel);
}
public List<Order> getUserOrders(String userId) {
List<OrderReadModel> readModels = readModelRepository.findByUserId(userId);
return readModels.stream()
.map(this::convertToOrder)
.collect(Collectors.toList());
}
private Order convertToOrder(OrderReadModel readModel) {
Order order = new Order();
order.setOrderId(readModel.getOrderId());
order.setUserId(readModel.getUserId());
order.setAmount(readModel.getAmount());
order.setStatus(readModel.getStatus());
order.setItems(readModel.getItems());
order.setCreateTime(readModel.getCreateTime());
return order;
}
}
3. 状态重建
事件溯源的一个强大特性是可以从事件流中重建状态:
@Service
public class OrderStateRebuilder {
@Autowired
private EventStore eventStore;
public Order rebuildOrder(String orderId) {
// 1. 获取订单的所有事件
List<Event> events = eventStore.getEvents(orderId);
// 2. 按顺序重放事件
Order order = new Order();
for (Event event : events) {
applyEvent(order, event);
}
return order;
}
private void applyEvent(Order order, Event event) {
if (event instanceof OrderCreatedEvent) {
OrderCreatedEvent e = (OrderCreatedEvent) event;
order.setOrderId(e.getOrderId());
order.setUserId(e.getUserId());
order.setAmount(e.getAmount());
order.setStatus(OrderStatus.PENDING);
} else if (event instanceof OrderPaidEvent) {
order.setStatus(OrderStatus.PAID);
} else if (event instanceof OrderCancelledEvent) {
order.setStatus(OrderStatus.CANCELLED);
}
}
}
五、方案优势
1. 高性能读写分离
- 写操作:只负责存储和发布事件,性能很高
- 读操作:从专门的读模型中读取,可以针对查询场景优化
2. 完整的审计日志
- 所有的状态变化都以事件的形式记录下来
- 可以随时回放历史,追溯问题原因
3. 高度可扩展
- 读模型可以根据不同的查询场景进行优化
- 可以轻松添加新的读模型,不影响写操作
4. 最终一致性
- 通过事件机制保证最终一致性
- 可以通过重放事件修复数据不一致问题
六、实战经验总结
- 事件设计要合理:事件应该是不可变的,包含足够的信息
- 版本控制很重要:事件结构可能会变化,需要做好版本控制
- 事件存储要高效:事件存储的性能直接影响整个系统的性能
- 读模型要及时更新:可以通过消息队列保证事件的可靠传递
- 监控要到位:对事件的发布、处理、消费等环节进行监控
七、完整架构图
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ │ │ │ │ │
│ 客户端请求 │─────────>│ 命令处理器 │─────────>│ 事件存储 │
│ │ │ │ │ │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ │ │
│ │ 事件发布器 │ │
│ │ │ │
│ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ │ │
│ │ 事件处理器 │ │
│ │ │ │
│ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ │ │
└──────────────────│ 读模型 │ │
│ │ │
└─────────────┘ │
│ │
▼ │
┌─────────────┐ │
│ │ │
│ 查询服务 │ │
│ │ │
└─────────────┘ │
│ │
▼ │
┌─────────────┐ │
│ │ │
│ 客户端响应 │ │
│ │ │
└─────────────┘ │
│ │
▼ │
┌─────────────┐ │
│ │ │
│ 状态重建 │◄──────────────┘
│ │
└─────────────┘
八、适用场景
事件溯源 + CQRS 并不是银弹,它适用于以下场景:
1. 高并发读写分离
- 读操作远多于写操作
- 需要对读性能进行优化
- 写操作和读操作的性能要求不同
2. 审计要求高
- 需要完整的操作日志
- 需要追溯历史状态
- 需要支持数据回滚
3. 业务逻辑复杂
- 业务规则复杂,需要精确控制
- 需要支持复杂的业务流程
- 需要支持业务规则的动态调整
4. 分布式系统
- 需要保证最终一致性
- 需要支持服务解耦
- 需要支持服务自治
九、写在最后
事件溯源 + CQRS 是一种强大的架构模式,它从根本上改变了我们设计和实现系统的方式。
通过事件溯源,我们可以完整地记录业务的发生过程;通过CQRS,我们可以实现高性能的读写分离。这两者结合,为我们提供了一种全新的思路来构建高一致性与高性能的系统。
当然,这种架构模式也有它的复杂度和学习成本。但在合适的场景下,它带来的收益是巨大的。
希望这篇文章能给你带来一些启发,帮助你在实际项目中更好地应用事件溯源和CQRS。
如果你在事件溯源和CQRS的实践中有其他经验或困惑,欢迎在评论区留言交流!
服务端技术精选,专注分享后端开发实战经验,让技术落地更简单。
如果你觉得这篇文章有用,欢迎点赞、在看、分享三连!
标题:SpringBoot + 事件溯源 + CQRS:高一致性与高性能读写分离架构
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/02/10/1770536593883.html
- 一、你的系统是不是也遇到了这些问题?
- 二、传统架构的困境
- 1. 读写冲突
- 2. 性能瓶颈
- 3. 数据一致性
- 4. 审计困难
- 三、事件溯源 + CQRS:架构设计的革命
- 1. 事件溯源(Event Sourcing)
- 2. CQRS(Command Query Responsibility Segregation)
- 四、方案详解
- 1. 事件溯源的实现
- (1)事件定义
- (2)事件存储
- (3)事件发布
- 2. CQRS的实现
- (1)命令处理器
- (2)事件处理器
- (3)查询服务
- 3. 状态重建
- 五、方案优势
- 1. 高性能读写分离
- 2. 完整的审计日志
- 3. 高度可扩展
- 4. 最终一致性
- 六、实战经验总结
- 七、完整架构图
- 八、适用场景
- 1. 高并发读写分离
- 2. 审计要求高
- 3. 业务逻辑复杂
- 4. 分布式系统
- 九、写在最后
评论