本文目录导读:

在 PHP 项目中处理分布式事务,主要有以下几种主流方案,我会从实现难度、一致性强度、适用场景三个维度来分析。
核心痛点
在微服务或分布式架构下,一个业务操作往往跨越多个服务、多个数据库,传统数据库的 ACID 事务无法跨服务生效,因此需要引入分布式事务机制。
主流方案对比
| 方案 | 一致性 | 性能 | 侵入性 | 适用场景 |
|---|---|---|---|---|
| 2PC(两阶段提交) | 强一致 | 低 | 高 | 银行、金融核心系统 |
| TCC(Try-Confirm-Cancel) | 最终一致(业务层控制) | 中 | 高 | 扣款+发货等强约束场景 |
| 本地消息表 | 最终一致 | 高 | 中 | 异步解耦、非实时性要求 |
| MQ 事务消息(RocketMQ) | 最终一致 | 高 | 中 | 异步解耦、通知类 |
| Saga(长事务) | 最终一致 | 高 | 中 | 聚合根之间的长链路业务 |
主流方案详解(含 PHP 代码实现)
方案 1:2PC(两阶段提交)
原理:协调者向所有参与者发送准备请求,全部 ACK 后发送提交指令。
PHP 实现示例:
class TwoPhaseCommit
{
private array $participants = [];
private array $preparedResources = [];
public function addParticipant(TransactionParticipant $participant): void
{
$this->participants[] = $participant;
}
public function execute(): bool
{
// Phase 1: Prepare
foreach ($this->participants as $participant) {
$result = $participant->prepare();
if (!$result) {
$this->rollback();
return false;
}
$this->preparedResources[] = $participant;
}
// Phase 2: Commit
foreach ($this->preparedResources as $participant) {
$participant->commit();
}
return true;
}
private function rollback(): void
{
foreach ($this->preparedResources as $participant) {
$participant->rollback();
}
}
}
⚠️ 缺点:阻塞式、性能差、协调者单点问题。
方案 2:TCC(Try-Confirm-Cancel)★ 推荐
原理:业务层实现 Try(预留资源)、Confirm(确认执行)、Cancel(回滚补偿)。
PHP 实现示例:
interface TccActionInterface
{
public function try(): bool;
public function confirm(): bool;
public function cancel(): bool;
}
class OrderTccService
{
private Redis $redis; // 存储事务执行状态
public function createOrder(Order $order): void
{
$txId = uniqid('tx_', true);
$status = new TransactionStatus($txId);
// Step 1: Try 阶段 - 各参与者预留资源
$reserveResult = $this->reserveInventory($order); // 扣减库存(冻结)
$deductResult = $this->deductBalance($order); // 扣减用户余额(冻结)
if ($reserveResult && $deductResult) {
// Step 2: Confirm 阶段
$this->confirmOrder($txId);
} else {
// Step 3: Cancel 阶段 - 回滚
$this->cancel($txId);
}
}
private function confirmOrder(string $txId): void
{
// 真实扣减,释放预占库存
// 执行下单确认逻辑
}
private function cancel(string $txId): void
{
// 释放库存预占
// 返还用户冻结金额
}
}
✅ 特点:
- 业务侵入性强(需要写
try/confirm/cancel三套逻辑) - 无锁、无阻塞、性能较好
- 必须处理好幂等性和空回滚问题
方案 3:本地消息表 ★ 最常用
原理:在事务性数据库中存消息记录,通过定时任务或 MQ 异步投递。
PHP 实现示例(Laravel + MySQL):
class OrderService
{
public function createOrder(array $orderData): void
{
// 开启本地事务
DB::transaction(function () use ($orderData) {
// 1. 创建订单
$order = Order::create($orderData);
// 2. 写入本地消息表(同库同事务)
Message::create([
'msg_key' => 'order_created',
'payload' => json_encode($order),
'status' => 'pending',
'retry_count' => 0,
]);
});
// 异步发送消息
$this->publishMessageToMQ();
}
private function publishMessageToMQ(): void
{
$pendingMessages = Message::where('status', 'pending')
->limit(100)
->get();
foreach ($pendingMessages as $message) {
// 发送到 RabbitMQ / Kafka
if (MQ::publish($message->msg_key, $message->payload)) {
$message->update(['status' => 'sent']);
}
}
}
}
✅ 优点:
- 实现简单,不依赖特定中间件
- 保证最终一致性
- 注意:需要维护消息表,定时扫描重发
方案 4:MQ 事务消息(RocketMQ)★ 高性能推荐
原理:利用 RocketMQ 的半事务消息机制,本地事务执行后提交,MQ 自动重试投递。
<?php
use RocketMQ\Producer;
use RocketMQ\TransactionListener;
use RocketMQ\Message;
class OrderTransactionListener implements TransactionListener
{
public function executeLocalTransaction(Message $msg): string
{
$orderData = json_decode($msg->getBody(), true);
// 本地事务
try {
DB::transaction(function () use ($orderData) {
Order::create($orderData);
});
return LocalTransactionState::COMMIT;
} catch (\Exception $e) {
return LocalTransactionState::ROLLBACK;
}
}
public function checkLocalTransaction(Message $msg): string
{
$orderId = $msg->getProperty('order_id');
$order = Order::find($orderId);
return $order ? LocalTransactionState::COMMIT : LocalTransactionState::ROLLBACK;
}
}
// 发送事务消息
$producer = new Producer();
$producer->start();
$msg = new Message('order-topic', json_encode($orderData));
$msg->setProperty('order_id', $order->id);
$result = $producer->sendMessageInTransaction($msg, new OrderTransactionListener());
✅ 特点:
- 无本地消息表,简洁
- 需要部署 RocketMQ(或阿里云 MQ)
- 适合消息可靠性要求高的场景
推荐架构组合策略
在实际 PHP 项目中,建议根据业务场景组合使用:
┌─────────────────────────────────────────────────────┐
│ 业务场景 │
├─────────────────┬───────────────────────────────────┤
│ 强一致性核心链路 │ 2PC / TCC │
│ 用户余额/库存 │ │
│ │ │
│ 最终一致性场景 │ 本地消息表 / MQ 事务消息 │
│ 订单通知/积分 │ │
│ │ │
│ 长链路聚合根 │ Saga(编排式/协同式) │
│ 跨 N 个服务 │ │
└─────────────────┴───────────────────────────────────┘
实战中的关键注意事项
幂等性设计(重中之重)
class IdempotencyUtil
{
public static function isProcessed(string $key): bool
{
$redis = app('redis');
return (bool) $redis->setnx("idempotent:{$key}", 1);
}
public static function release(string $key): void
{
app('redis')->del("idempotent:{$key}");
}
}
补偿机制
- 定时扫描未完成的事务记录
- 超时自动触发 Cancel 或补偿重试
- 记录完整的事务日志便于人工干预
监控与告警
// 记录分布式事务状态到 ELK / Prometheus
Metrics::increment('distributed_tx_total');
Metrics::increment('distributed_tx_failed');
总结建议
| 项目阶段 | 推荐方案 | 原因 |
|---|---|---|
| 早期单体 + 分库 | 本地消息表 | 简单可靠,无运维成本 |
| 中期微服务化 | TCC + 本地消息表 | 兼顾强一致和最终一致 |
| 大规模高并发 | RocketMQ 事务消息 + Saga | 性能最优,运维成熟 |
| 要求强一致 | 2PC / 分布式数据库(如 TiDB) | 数据一致性要求高于性能 |
最终原则:
- 非必要不分布式事务,尽量通过事件溯源 + 最终一致性解决。
- 将业务边界划分清晰,减少跨服务事务。
- 每个服务必须保障自己数据的完整性,通过补偿而非全局锁。
根据你的项目规模、团队运维能力和业务一致性要求,选择合适的方案组合即可,如果只是小规模项目,先推荐本地消息表方案,侵入性低且实现成本最低。