PHP项目分布式事务怎样处理

wen PHP项目 4

本文目录导读:

PHP项目分布式事务怎样处理

  1. 核心痛点
  2. 主流方案对比
  3. 主流方案详解(含 PHP 代码实现)
  4. 推荐架构组合策略
  5. 实战中的关键注意事项
  6. 总结建议

在 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) 数据一致性要求高于性能

最终原则

  • 非必要不分布式事务,尽量通过事件溯源 + 最终一致性解决。
  • 将业务边界划分清晰,减少跨服务事务。
  • 每个服务必须保障自己数据的完整性,通过补偿而非全局锁。

根据你的项目规模、团队运维能力和业务一致性要求,选择合适的方案组合即可,如果只是小规模项目,先推荐本地消息表方案,侵入性低且实现成本最低。

抱歉,评论功能暂时关闭!