本文目录导读:

我来详细讲解PHP中分布式事务TCC模式的实现。
TCC模式概述
TCC(Try-Confirm-Cancel)是一种补偿性分布式事务解决方案,包含三个阶段:
Try:预留业务资源
2. Confirm:确认执行业务
3. Cancel:取消预留资源
核心架构设计
基础接口定义
<?php
// TCC事务参与者接口
interface TccParticipant {
// 预留资源
public function try($transactionId, $params);
// 确认执行
public function confirm($transactionId);
// 取消操作
public function cancel($transactionId);
}
// 事务上下文
class TransactionContext {
private $transactionId;
private $status; // TRYING, CONFIRMING, CANCELLING
private $participants = [];
private $params = [];
public function __construct($transactionId) {
$this->transactionId = $transactionId;
$this->status = 'TRYING';
}
public function addParticipant(TccParticipant $participant, $params) {
$this->participants[] = [
'participant' => $participant,
'params' => $params
];
}
// getters/setters...
}
事务管理器
<?php
class TccTransactionManager {
private $cache;
private $logger;
private $maxRetries = 3;
public function __construct($cache, $logger) {
$this->cache = $cache;
$this->logger = $logger;
}
/**
* 执行TCC事务
*/
public function execute(callable $transactionFunc) {
$transactionId = $this->generateTransactionId();
$context = new TransactionContext($transactionId);
try {
// 阶段一:Try
$transactionFunc($context);
$this->executeTryPhase($context);
// 阶段二:Confirm
return $this->executeConfirmPhase($context);
} catch (Exception $e) {
// 阶段三:Cancel
$this->executeCancelPhase($context);
throw $e;
}
}
/**
* 执行Try阶段
*/
private function executeTryPhase(TransactionContext $context) {
$this->logger->info("开始Try阶段", ['transactionId' => $context->getTransactionId()]);
$successfulParticipants = [];
foreach ($context->getParticipants() as $index => $participantInfo) {
try {
$participant = $participantInfo['participant'];
$params = $participantInfo['params'];
// 执行Try
$result = $participant->try(
$context->getTransactionId(),
$params
);
$successfulParticipants[] = $index;
// 保存执行状态
$this->saveTransactionState($context, $participant, 'TRY_SUCCESS');
} catch (Exception $e) {
$this->logger->error("Try阶段失败", [
'transactionId' => $context->getTransactionId(),
'participant' => get_class($participant),
'error' => $e->getMessage()
]);
// 回滚已成功的Try操作
foreach ($successfulParticipants as $successIndex) {
$this->executeCancelForParticipant($context, $successIndex);
}
throw new TccException("Try阶段失败", 0, $e);
}
}
$context->setStatus('TRY_SUCCESS');
$this->saveContext($context);
}
/**
* 执行Confirm阶段
*/
private function executeConfirmPhase(TransactionContext $context) {
$this->logger->info("开始Confirm阶段", ['transactionId' => $context->getTransactionId()]);
try {
$results = [];
foreach ($context->getParticipants() as $index => $participantInfo) {
$participant = $participantInfo['participant'];
$retryCount = 0;
while ($retryCount < $this->maxRetries) {
try {
$result = $participant->confirm($context->getTransactionId());
$results[] = $result;
break;
} catch (Exception $e) {
$retryCount++;
if ($retryCount >= $this->maxRetries) {
$this->logger->error("Confirm阶段重试失败", [
'transactionId' => $context->getTransactionId(),
'participant' => get_class($participant),
'retryCount' => $retryCount
]);
// 记录异常,需要人工介入
$this->recordManualIntervention($context, $participant, $e);
}
sleep(1);
}
}
}
$context->setStatus('CONFIRM_SUCCESS');
$this->destroyContext($context);
return $results;
} catch (Exception $e) {
$this->logger->critical("Confirm阶段异常,需要人工处理", [
'transactionId' => $context->getTransactionId(),
'error' => $e->getMessage()
]);
throw $e;
}
}
/**
* 执行Cancel阶段
*/
private function executeCancelPhase(TransactionContext $context) {
$this->logger->info("执行Cancel阶段", ['transactionId' => $context->getTransactionId()]);
$participants = $context->getParticipants();
foreach ($participants as $index => $participantInfo) {
$this->executeCancelForParticipant($context, $index);
}
$context->setStatus('CANCEL_SUCCESS');
$this->saveContext($context);
}
private function executeCancelForParticipant($context, $index) {
$participantInfo = $context->getParticipants()[$index];
$participant = $participantInfo['participant'];
try {
$participant->cancel($context->getTransactionId());
$this->logger->info("Cancel成功", [
'transactionId' => $context->getTransactionId(),
'participant' => get_class($participant)
]);
} catch (Exception $e) {
$this->logger->error("Cancel失败", [
'transactionId' => $context->getTransactionId(),
'participant' => get_class($participant),
'error' => $e->getMessage()
]);
// 保存状态以便后续补偿
$this->saveCancellationFailure($context, $participant);
}
}
// 生成事务ID
private function generateTransactionId() {
return uniqid('tcc_', true) . bin2hex(random_bytes(8));
}
// 保存事务状态到缓存
private function saveContext(TransactionContext $context) {
$key = "tcc:transaction:{$context->getTransactionId()}";
$this->cache->set($key, serialize($context), 3600);
}
private function destroyContext(TransactionContext $context) {
$key = "tcc:transaction:{$context->getTransactionId()}";
$this->cache->delete($key);
}
// 获取事务上下文
public function getContext($transactionId) {
$key = "tcc:transaction:{$transactionId}";
$data = $this->cache->get($key);
return $data ? unserialize($data) : null;
}
// 保存状态
private function saveTransactionState($context, $participant, $status) {
// 保存到数据库或日志
$state = [
'transaction_id' => $context->getTransactionId(),
'participant' => get_class($participant),
'status' => $status,
'time' => date('Y-m-d H:i:s')
];
$this->logger->info("保存事务状态", $state);
}
private function recordManualIntervention($context, $participant, $e) {
$this->logger->critical("需要人工介入的异常事务", [
'transaction_id' => $context->getTransactionId(),
'participant' => get_class($participant),
'status' => 'MANUAL_INTERVENTION_REQUIRED',
'exception' => $e->getMessage()
]);
}
private function saveCancellationFailure($context, $participant) {
$this->logger->error("取消操作失败,等待后续处理", [
'transaction_id' => $context->getTransactionId(),
'participant' => get_class($participant),
'status' => 'CANCEL_FAILED'
]);
}
}
// 自定义异常
class TccException extends Exception {}
具体参与者实现示例
<?php
// 账户服务参与者
class AccountServiceParticipant implements TccParticipant {
private $db;
public function __construct($db) {
$this->db = $db;
}
/**
* Try阶段:冻结资金
*/
public function try($transactionId, $params) {
$accountId = $params['account_id'];
$amount = $params['amount'];
$this->db->beginTransaction();
try {
// 检查余额是否充足
$balance = $this->db->query(
"SELECT balance FROM accounts WHERE id = ? FOR UPDATE",
[$accountId]
);
if ($balance < $amount) {
throw new \Exception("余额不足");
}
// 创建冻结记录
$this->db->execute(
"INSERT INTO account_frozen (transaction_id, account_id, amount, status, created_at)
VALUES (?, ?, ?, 'FROZEN', NOW())",
[$transactionId, $accountId, $amount]
);
// 更新可用余额
$this->db->execute(
"UPDATE accounts SET frozen_amount = frozen_amount + ? WHERE id = ?",
[$amount, $accountId]
);
$this->db->commit();
return true;
} catch (Exception $e) {
$this->db->rollback();
throw $e;
}
}
/**
* Confirm阶段:完成资金扣减
*/
public function confirm($transactionId) {
$this->db->beginTransaction();
try {
// 获取冻结记录
$frozen = $this->db->query(
"SELECT * FROM account_frozen WHERE transaction_id = ? AND status = 'FROZEN'",
[$transactionId]
);
if (!$frozen) {
throw new \Exception("无效的冻结记录");
}
// 更新账户余额
$this->db->execute(
"UPDATE accounts SET
balance = balance - ?,
frozen_amount = frozen_amount - ?
WHERE id = ?",
[$frozen['amount'], $frozen['amount'], $frozen['account_id']]
);
// 更新冻结记录状态
$this->db->execute(
"UPDATE account_frozen SET status = 'CONFIRMED', confirmed_at = NOW()
WHERE transaction_id = ?",
[$transactionId]
);
$this->db->commit();
return true;
} catch (Exception $e) {
$this->db->rollback();
throw $e;
}
}
/**
* Cancel阶段:释放冻结资金
*/
public function cancel($transactionId) {
$this->db->beginTransaction();
try {
// 获取冻结记录
$frozen = $this->db->query(
"SELECT * FROM account_frozen WHERE transaction_id = ? AND status = 'FROZEN'",
[$transactionId]
);
if ($frozen) {
// 解冻资金
$this->db->execute(
"UPDATE accounts SET frozen_amount = frozen_amount - ? WHERE id = ?",
[$frozen['amount'], $frozen['account_id']]
);
// 更新冻结记录状态
$this->db->execute(
"UPDATE account_frozen SET status = 'CANCELLED', cancelled_at = NOW()
WHERE transaction_id = ?",
[$transactionId]
);
}
$this->db->commit();
return true;
} catch (Exception $e) {
$this->db->rollback();
throw $e;
}
}
}
库存服务参与者
<?php
// 库存服务参与者
class InventoryServiceParticipant implements TccParticipant {
private $redis;
private $db;
public function __construct($redis, $db) {
$this->redis = $redis;
$this->db = $db;
}
/**
* Try阶段:预扣库存
*/
public function try($transactionId, $params) {
$productId = $params['product_id'];
$quantity = $params['quantity'];
$this->db->beginTransaction();
try {
// 检查库存
$inventory = $this->db->query(
"SELECT * FROM inventory WHERE product_id = ? FOR UPDATE",
[$productId]
);
if ($inventory['quantity'] < $quantity) {
throw new \Exception("库存不足");
}
// 创建预扣记录
$this->db->execute(
"INSERT INTO inventory_reservation (transaction_id, product_id, quantity, status, created_at)
VALUES (?, ?, ?, 'RESERVED', NOW())",
[$transactionId, $productId, $quantity]
);
$this->db->commit();
return true;
} catch (Exception $e) {
$this->db->rollback();
throw $e;
}
}
// confirm和cancel方法类似...
}
使用示例
<?php
// 使用TCC事务示例
$tccManager = new TccTransactionManager($cache, $logger);
try {
$results = $tccManager->execute(function($context) use ($userId, $productId, $amount, $accountService, $inventoryService) {
// 添加账户扣款参与者
$context->addParticipant($accountService, [
'account_id' => $userId,
'amount' => $amount
]);
// 添加库存扣减参与者
$context->addParticipant($inventoryService, [
'product_id' => $productId,
'quantity' => 1
]);
// 可以添加更多参与者
});
echo "事务执行成功";
} catch (TccException $e) {
echo "事务执行失败:" . $e->getMessage();
}
异常补偿机制
<?php
// 补偿任务管理器
class CompensationManager {
private $db;
private $logger;
/**
* 扫描并处理失败的Cancel操作
*/
public function processFailedCancellations() {
$query = "SELECT * FROM transaction_compensation
WHERE status = 'PENDING' OR status = 'RETRYING'";
$records = $this->db->query($query);
foreach ($records as $record) {
$this->retryCompensation($record);
}
}
/**
* 重试补偿操作
*/
private function retryCompensation($record) {
$maxAttempts = 3;
$attempt = 0;
while ($attempt < $maxAttempts) {
try {
// 重新执行Cancel
$participant = $this->getParticipant($record['participant_class']);
$participant->cancel($record['transaction_id']);
// 更新状态
$this->db->execute(
"UPDATE transaction_compensation SET status = 'COMPLETED',
completed_at = NOW() WHERE id = ?",
[$record['id']]
);
break;
} catch (Exception $e) {
$attempt++;
sleep(2 * $attempt); // 递增等待时间
if ($attempt >= $maxAttempts) {
$this->logger->error("补偿操作最终失败", [
'recordId' => $record['id'],
'transactionId' => $record['transaction_id']
]);
// 标记为失败,需要人工处理
$this->db->execute(
"UPDATE transaction_compensation SET status = 'NEED_MANUAL',
error_message = ? WHERE id = ?",
[$e->getMessage(), $record['id']]
);
}
}
}
}
}
数据库表结构
-- 冻结记录表
CREATE TABLE account_frozen (
id INT AUTO_INCREMENT PRIMARY KEY,
transaction_id VARCHAR(64) NOT NULL,
account_id INT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status ENUM('FROZEN', 'CONFIRMED', 'CANCELLED') DEFAULT 'FROZEN',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
confirmed_at TIMESTAMP NULL,
cancelled_at TIMESTAMP NULL,
INDEX idx_transaction_id (transaction_id),
INDEX idx_account_id (account_id)
);
-- 库存预留表
CREATE TABLE inventory_reservation (
id INT AUTO_INCREMENT PRIMARY KEY,
transaction_id VARCHAR(64) NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
status ENUM('RESERVED', 'CONFIRMED', 'CANCELLED') DEFAULT 'RESERVED',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_transaction_id (transaction_id)
);
-- 事务状态表
CREATE TABLE tcc_transaction (
id INT AUTO_INCREMENT PRIMARY KEY,
transaction_id VARCHAR(64) NOT NULL,
status VARCHAR(20) NOT NULL,
participants TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_transaction_id (transaction_id)
);
-- 需要人工干预的事务记录表
CREATE TABLE tcc_manual_intervention (
id INT AUTO_INCREMENT PRIMARY KEY,
transaction_id VARCHAR(64) NOT NULL,
participant_class VARCHAR(200),
error_message TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status ENUM('PENDING', 'PROCESSED') DEFAULT 'PENDING'
);
最佳实践和注意事项
幂等性设计
// 幂等性检查示例
class IdempotencyHandler {
private $redis;
public function check($transactionId, $operation) {
$key = "tcc:idempotency:{$transactionId}:{$operation}";
// 使用Redis的原子操作
$result = $this->redis->setnx($key, 1);
if ($result) {
// 设置过期时间(例如10分钟)
$this->redis->expire($key, 600);
return true; // 首次操作
}
return false; // 重复操作
}
}
超时处理
// 设置合理的超时时间
class TimeoutHandler {
const TRY_TIMEOUT = 5000; // 5秒
const CONFIRM_TIMEOUT = 10000; // 10秒
const CANCEL_TIMEOUT = 10000; // 10秒
public function setTimeout($operation, $timeout) {
// 设置HTTP客户端超时
$this->client->setTimeout($timeout);
// 设置数据库操作超时
$this->db->setQueryTimeout($timeout);
}
}
性能优化建议
- 使用连接池管理数据库连接
- 批量处理操作命令
- 使用异步消息队列处理非核心业务
- 合理设置缓存过期时间
// 连接池示例
class ConnectionPool {
private $pool = [];
private $maxSize = 10;
public function getConnection() {
// 从池中获取连接或创建新连接
}
public function releaseConnection($connection) {
// 归还连接
}
}
TCC模式通过业务拆分配置,提供了可靠的分布式事务解决方案,特别适合对数据一致性要求较高的金融、电商等场景,但要注意其性能开销相对较高,需要根据业务场景合理选择。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播