本文目录导读:

在PHP项目中,实现分布式事务是一个比较复杂但非常重要的课题,由于PHP本身是无状态的(通常每个请求结束后资源被回收),传统的XA协议(两阶段提交)在PHP中实现起来成本较高且性能较差。
目前主流的解决方案主要分为三大流派:尽力而为(最终一致性)、TCC(Try-Confirm-Cancel) 和 Saga模式。
以下为你详细拆解这些方案在PHP项目中的具体实现方式、适用场景及代码思路。
核心原则:放弃强一致性,拥抱最终一致性
在处理PHP分布式事务时,建议首先明确一个前提:除非使用TCC模式且引入全局锁,否则几乎不可能实现强一致性(ACID),绝大多数PHP项目(电商、社交、SaaS)都采用BASE理论(基本可用、软状态、最终一致)。
主流方案详解
本地消息表 + 消息队列(最常用、最稳妥)
这是PHP项目中最推荐的方案,它通过数据库本地事务和消息队列结合,将分布式事务拆解为多个本地事务。
核心思想:
- 生产者:在一个数据库本地事务中,同时执行业务操作(如扣库存)和插入一条消息记录(到本地消息表)。
- 异步任务:一个后台守护进程(如Workerman、Swoole常驻进程或定时器)扫描消息表,将消息发送到MQ(如RabbitMQ、Kafka、Redis Stream)。
- 消费者:另一个服务消费MQ消息,执行下游业务(如增加积分、发物流单),如果失败,MQ重试机制会保证最终成功。
PHP代码示例(伪代码):
// 1. 订单服务(生产者)
public function createOrder($userId, $productId, $amount) {
$db->beginTransaction();
try {
// 业务操作:扣库存
$this->deductStock($productId, $amount);
// 业务操作:生成本地消息(状态:待发送)
$this->messageRepo->insert([
'type' => 'order_created',
'payload' => json_encode(['order_id' => 123, 'user_id' => $userId]),
'status' => 0 // 0:待发送, 1:已发送, 2:已消费
]);
$db->commit();
} catch (Exception $e) {
$db->rollback();
throw $e;
}
}
// 2. 后台任务(PHP常驻进程或Cron Job)
// 每次启动或定时执行
function processLocalMessages() {
$messages = $this->messageRepo->findByStatus(0);
foreach ($messages as $msg) {
try {
// 发送到MQ(RabbitMQ)
$this->mq->publish('order.topic', $msg['payload']);
// 更新消息状态为“已发送”
$this->messageRepo->updateStatus($msg['id'], 1);
} catch (Exception $e) {
// 记录日志,等待下次重试,注意幂等性。
logError("Failed to send message: " . $msg['id']);
}
}
}
- 优点:实现相对简单,数据不会丢失(依赖MySQL事务),性能较好。
- 缺点:业务代码与消息表耦合,需要维护后台扫描脚本。
- 适用场景:绝大多数业务(订单、支付、积分、物流)。
TCC 模式(Try-Confirm-Cancel)
TCC要求每个服务提供三个接口:Try(预留资源)、Confirm(确认执行)、Cancel(取消/回滚)。
核心思想:
- Try阶段:检查资源并预留(冻结库存100元,而非直接扣减),如果所有Try成功,则进入Confirm。
- Confirm阶段:执行业务(将冻结的库存实际扣减),如果Confirm失败,会不断重试。
- Cancel阶段:如果某个Try失败或超时,对所有已成功的Try执行Cancel(解冻库存)。
PHP实现难点:
- 需要写大量的补偿逻辑。
- 需要维护一个事务协调器(TC),通常用Swoole或Workerman来实现(因为需要维护状态和超时)。
- 高并发下锁竞争激烈。
代码示意(偏宏大):
// 订单服务的 Try
public function tryCreateOrder($orderData) {
// 1. 检查库存是否充足
// 2. 将库存从 "可用库存" 移到 "冻结库存"
// 3. 返回一个全局事务ID
return $globalTxId;
}
// 订单服务的 Confirm
public function confirmCreateOrder($globalTxId) {
// 1. 将 "冻结库存" 扣减为0
// 2. 生成真实订单
}
// 订单服务的 Cancel
public function cancelCreateOrder($globalTxId) {
// 1. 将 "冻结库存" 加回 "可用库存"
// 2. 删除临时订单
}
- 优点:性能高(Try阶段不阻塞,业务补偿可控)。
- 缺点:开发成本非常高,接口设计复杂,排查问题困难。
- 适用场景:对一致性要求极高、短事务、高并发场景(如红包、秒杀)。
Saga 模式(基于事件/编排)
Saga将一个长事务拆分成多个本地事务,并通过事件驱动或编排器来串联。
- Choreography(编排):服务间通过事件互相调用。
- Orchestration(协调器):一个中心协调器(Saga Manager)调度各个服务。
PHP实现思路:
- 使用一个协调器服务(可以是Swoole常驻进程)。
- 协调器向订单服务发送
executePayment命令。 - 订单服务成功后,协调器向积分服务发送
addPoints命令。 - 如果积分服务失败,协调器向订单服务发送
reversePayment补偿命令。
PHP代码示意(使用协程):
class SagaOrchestrator {
public function run($orderId) {
$state = new SagaState($orderId);
// 第一步:执行支付
try {
$result = $this->paymentService->execute($orderId);
$state->stepDone('payment', $result);
} catch (Exception $e) {
return $state->fail(); // 直接失败,无补偿
}
// 第二步:执行积分
try {
$this->pointsService->addPoints($orderId, 100);
$state->stepDone('points');
} catch (Exception $e) {
// 积分失败,补偿支付
$this->paymentService->reverse($orderId);
return $state->fail('rollback payment');
}
return $state->success();
}
}
- 优点:易于理解和实现,适合复杂长流程。
- 缺点:协调器是单点,流程越长故障风险越大。
- 适用场景:跨多个微服务的复杂业务流程(如下单->支付->发货->评价)。
常见陷阱与最佳实践
- 幂等性是生存之本:所有消息处理的消费者必须实现幂等,即使是同一个消息被重复投递(MQ重试、网络重试),执行一次和执行多次的结果必须一致。
- 不要使用PHP的
session或apc做状态存储:分布式环境下需要可靠的外部存储(Redis、MySQL)。 - 监控与日志:必须有完善的日志链路追踪(Trace ID),以便在发生数据不一致时人工介入补偿。
- 不要试图自己造轮子:PHP生态里有一些比较好的框架和组件可以参考(如果使用Swoole):比如Seata(阿里开源的分布式事务框架,有PHP客户端扩展)、Hyperf(Swoole框架内置TCC和Saga支持)。
针对不同PHP项目的选型建议
| 项目类型 | 推荐方案 | 理由 |
|---|---|---|
| 传统Laravel/Symfony项目 | 本地消息表 | 简单、可靠,无需引入复杂框架,只要MySQL + 消息队列即可。 |
| 高并发秒杀/支付 | TCC | 需要精细控制资源,Try阶段只是检查,确认才执行,性能最高。 |
| 长业务流程(如报关、审核) | Saga | 业务状态机非常清晰,每一步失败都有明确的补偿。 |
| 现有单体应用重构微服务 | 本地消息表 | 改动最小,风险最低,可以逐步替换。 |
一句话总结:对于绝大多数PHP项目,使用本地消息表方案即可解决95%的分布式事务问题,只有当业务量极高或流程极其复杂时,才需要投入资源考虑TCC或Saga。