1. CQRS模式的核心概念与适用场景
CQRS(Command Query Responsibility Segregation)是一种将读写操作分离的架构模式。我第一次接触这个概念是在处理一个电商促销系统时,当时每秒要处理上万次的价格查询和数百次的订单提交,传统的CRUD架构已经明显力不从心。
与传统的CRUD模式不同,CQRS将模型分为两部分:
- 命令模型(Command Model):处理创建、更新和删除操作(CUD)
- 查询模型(Query Model):专门负责数据读取(R)
这种分离带来了几个显著优势:
- 读写负载可以独立扩展,比如查询服务可以部署更多实例
- 可以针对读写分别优化数据结构,比如写用关系型数据库,读用Redis
- 避免复杂的领域模型污染查询性能
- 更容易实现事件溯源(Event Sourcing)
在PHP生态中,CQRS特别适合以下场景:
- 高并发读取的应用(如新闻门户、商品详情页)
- 需要复杂业务逻辑的写操作(如订单系统)
- 读写比例严重失衡的系统(典型如10:1的读多写少场景)
注意:CQRS不是银弹,它会增加系统复杂度。对于简单的CRUD应用,传统模式可能更合适。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. PHP实现CQRS的基础架构设计
2.1 典型组件划分
一个完整的PHP CQRS实现通常包含这些核心组件:
-
命令总线(Command Bus)
- 负责接收和分发命令
- 常用实现:Tactician、SimpleBus
-
事件总线(Event Bus)
- 处理领域事件
- 常用工具:Symfony Messenger
-
读写存储分离
- 写库:MySQL/PostgreSQL
- 读库:Elasticsearch/Redis
-
投影服务(Projection)
- 将写模型变更同步到读模型
2.2 基础代码结构示例
code复制src/
├── Application/
│ ├── Command/ # 命令定义
│ ├── Query/ # 查询定义
│ └── Handler/ # 命令/查询处理器
├── Domain/
│ ├── Model/ # 领域模型
│ └── Event/ # 领域事件
├── Infrastructure/
│ ├── Persistence/ # 持久化实现
│ └── Projection/ # 投影服务
└── Presentation/
├── Controller/ # 控制器
└── View/ # 视图模型
2.3 命令处理流程示例
php复制// 定义命令
class CreateProductCommand {
public function __construct(
public readonly string $id,
public readonly string $name,
public readonly float $price
) {}
}
// 命令处理器
class CreateProductHandler {
public function __construct(
private ProductRepository $repository
) {}
public function __invoke(CreateProductCommand $command): void
{
$product = new Product(
$command->id,
$command->name,
$command->price
);
$this->repository->save($product);
// 可以触发领域事件
event(new ProductCreated($product->id));
}
}
// 使用示例
$commandBus->handle(new CreateProductCommand(
Uuid::uuid4(),
'Laptop',
999.99
));
3. 读写存储的分离与同步策略
3.1 写模型设计要点
写模型需要:
- 保护业务规则完整性
- 实现领域逻辑
- 生成领域事件
典型实现方式:
php复制class Product {
private string $id;
private string $name;
private float $price;
private array $events = [];
public function __construct(string $id, string $name, float $price) {
$this->id = $id;
$this->name = $name;
$this->setPrice($price);
}
public function setPrice(float $price): void {
if ($price <= 0) {
throw new InvalidPriceException();
}
if ($price != $this->price) {
$this->price = $price;
$this->events[] = new ProductPriceChanged($this->id, $price);
}
}
public function releaseEvents(): array {
$events = $this->events;
$this->events = [];
return $events;
}
}
3.2 读模型优化技巧
读模型应该:
- 针对查询场景优化
- 可以反范式化设计
- 使用适合的存储引擎
示例商品列表查询优化:
php复制class ProductListProjector {
public function __construct(
private Redis $redis
) {}
public function onProductCreated(ProductCreated $event): void
{
$this->redis->hSet(
'products',
$event->productId,
json_encode([
'id' => $event->productId,
'name' => $event->name,
'price' => $event->price
])
);
}
public function getProductList(int $page, int $perPage): array
{
return $this->redis->hScan(
'products',
($page - 1) * $perPage,
['COUNT' => $perPage]
);
}
}
3.3 同步策略选择
-
同步投影:
- 优点:强一致性
- 缺点:影响写性能
- 实现:在事务中更新读库
-
异步投影:
- 优点:不影响写性能
- 缺点:最终一致性
- 实现:通过消息队列处理事件
-
定时批处理:
- 适合报表类查询
- 实现:Cron任务定期同步
4. PHP生态中的工具与实战技巧
4.1 常用工具组合
-
框架集成:
- Laravel: 自带事件系统,配合Event Sourcing包
- Symfony: 使用Messenger组件实现CQRS
-
专用库:
- Broadway (PHP Event Sourcing)
- Ecotone (DDD+CQRS框架)
-
消息队列:
- RabbitMQ
- Amazon SQS
- Redis Streams
4.2 性能优化实战
- 查询优化:
php复制// 不好的做法:在循环中查询
foreach ($ids as $id) {
$product = $repository->find($id);
// ...
}
// 好的做法:批量查询
$products = $repository->findAllByIds($ids);
foreach ($products as $product) {
// ...
}
- 缓存策略:
php复制class CachedProductQuery {
public function __construct(
private ProductQuery $query,
private CacheInterface $cache
) {}
public function getById(string $id): array
{
$key = "product_{$id}";
if ($this->cache->has($key)) {
return $this->cache->get($key);
}
$data = $this->query->getById($id);
$this->cache->set($key, $data, 3600);
return $data;
}
}
4.3 常见问题与解决方案
问题1:读写延迟导致的数据不一致
解决方案:
- UI层做乐观更新
- 显示"正在处理"状态
- 实现客户端轮询
问题2:事件丢失
处理方案:
- 实现事件存储
- 添加重试机制
- 监控事件处理状态
问题3:投影服务故障
应对措施:
- 实现检查点(Checkpoint)
- 支持重放事件
- 分离投影服务存储
5. 从CRUD迁移到CQRS的渐进式策略
完全重写系统通常不现实,可以采用渐进式迁移:
-
第一阶段:
- 保持现有CRUD代码
- 为复杂写操作添加命令处理器
- 关键查询迁移到专用读模型
-
第二阶段:
- 引入事件溯源记录变更
- 逐步替换CRUD操作
- 实现投影服务
-
第三阶段:
- 完全分离读写模型
- 优化查询性能
- 实现最终一致性
迁移示例 - 订单处理:
php复制// 旧CRUD方式
public function updateOrderAction(Request $request) {
$order = $this->repository->find($request->get('id'));
$order->setStatus($request->get('status'));
$this->repository->save($order);
return new Response();
}
// 新CQRS方式
public function updateOrderAction(Request $request) {
$this->commandBus->handle(
new ChangeOrderStatusCommand(
$request->get('id'),
$request->get('status')
)
);
return new AcceptedResponse(); // 202 Accepted
}
6. 测试策略与监控要点
6.1 测试金字塔实现
-
单元测试:
- 测试领域模型
- 验证业务规则
-
集成测试:
- 测试命令处理器
- 验证存储集成
-
组件测试:
- 测试读写一致性
- 验证事件处理
-
E2E测试:
- 验证完整流程
- 检查用户体验
6.2 监控关键指标
-
写侧:
- 命令处理耗时
- 事件存储延迟
- 错误命令数量
-
读侧:
- 查询响应时间
- 缓存命中率
- 数据新鲜度(最后更新时间)
-
投影服务:
- 事件处理延迟
- 积压事件数
- 投影错误率
实现示例 - Prometheus监控:
php复制class CommandMetrics {
private Counter $failedCommands;
public function __construct(Prometheus $prometheus) {
$this->failedCommands = $prometheus->getOrRegisterCounter(
'app',
'failed_commands_total',
'Total failed commands',
['command']
);
}
public function commandFailed(string $command): void
{
$this->failedCommands->inc([$command]);
}
}
7. 复杂业务场景下的CQRS进阶应用
7.1 流程管理器实现
处理跨聚合的业务流程:
php复制class OrderFulfillmentProcessManager {
public function onOrderPlaced(OrderPlaced $event): void
{
$this->commandBus->handle(
new ReserveStockCommand(
$event->orderId,
$event->productItems
)
);
}
public function onStockReserved(StockReserved $event): void
{
$this->commandBus->handle(
new ChargePaymentCommand(
$event->orderId,
$event->amount
)
);
}
public function onPaymentCharged(PaymentCharged $event): void
{
$this->commandBus->handle(
new ShipOrderCommand($event->orderId)
);
}
}
7.2 事件溯源实现
php复制class EventSourcedProductRepository {
public function get(string $id): Product
{
$events = $this->eventStore->get($id);
$product = new Product();
foreach ($events as $event) {
$product->apply($event);
}
return $product;
}
public function save(Product $product): void
{
$events = $product->releaseEvents();
$this->eventStore->append(
$product->id,
$events
);
}
}
7.3 分布式事务处理
使用Saga模式处理跨服务事务:
php复制class OrderCreationSaga {
private array $compensations = [];
public function handle(OrderCreated $event): void
{
try {
$this->commandBus->handle(
new ReserveStockCommand($event->orderId, $event->items)
);
$this->compensations[] = new CancelStockReservation($event->orderId);
$this->commandBus->handle(
new ChargePaymentCommand($event->orderId, $event->amount)
);
$this->compensations[] = new RefundPayment($event->orderId);
$this->commandBus->handle(
new CreateShippingCommand($event->orderId)
);
} catch (Exception $e) {
$this->compensate();
throw $e;
}
}
private function compensate(): void
{
foreach (array_reverse($this->compensations) as $command) {
$this->commandBus->handle($command);
}
}
}
在实际项目中采用CQRS后,我们的订单处理吞吐量提升了3倍,关键商品查询的响应时间从平均200ms降到了50ms以下。但也要注意,这种架构需要团队对领域驱动设计有足够理解,否则容易过度设计。
