在PHP高并發(fā)場景下,解決超賣和超扣問題需要從多個方面入手,以下是一些常見的解決方案:
1. 數(shù)據(jù)庫鎖機(jī)制
1.1 悲觀鎖
在事務(wù)中通過 SELECT ... FOR UPDATE 鎖定記錄,確保同一時間只有一個請求能修改數(shù)據(jù)。
$pdo->beginTransaction();
$stmt = $pdo->prepare("SELECT stock FROM products WHERE id = :id FOR UPDATE");
$stmt->execute([':id' => $productId]);
$stock = $stmt->fetchColumn();
if ($stock > 0) {
$stmt = $pdo->prepare("UPDATE products SET stock = stock - 1 WHERE id = :id");
$stmt->execute([':id' => $productId]);
$pdo->commit();
} else {
$pdo->rollBack();
throw new Exception("庫存不足");
}
1.2 樂觀鎖
通過版本號或時間戳控制并發(fā),更新時檢查版本號是否一致。
$pdo->beginTransaction();
$stmt = $pdo->prepare("SELECT stock, version FROM products WHERE id = :id");
$stmt->execute([':id' => $productId]);
$product = $stmt->fetch(PDO::FETCH_ASSOC);
if ($product['stock'] > 0) {
$stmt = $pdo->prepare("UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = :id AND version = :version");
$stmt->execute([':id' => $productId, ':version' => $product['version']]);
if ($stmt->rowCount() > 0) {
$pdo->commit();
} else {
$pdo->rollBack();
throw new Exception("更新失敗,請重試");
}
} else {
$pdo->rollBack();
throw new Exception("庫存不足");
}
2. Redis 分布式鎖
使用Redis的 SETNX 命令實(shí)現(xiàn)分布式鎖,確保同一時間只有一個請求能執(zhí)行關(guān)鍵操作。
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$lockKey = 'product_lock_' . $productId;
$lockValue = uniqid();
$lockExpire = 10; // 鎖過期時間
if ($redis->set($lockKey, $lockValue, ['nx', 'ex' => $lockExpire])) {
try {
$stock = $redis->get('product_stock_' . $productId);
if ($stock > 0) {
$redis->decr('product_stock_' . $productId);
// 其他業(yè)務(wù)邏輯
} else {
throw new Exception("庫存不足");
}
} finally {
if ($redis->get($lockKey) === $lockValue) {
$redis->del($lockKey);
}
}
} else {
throw new Exception("系統(tǒng)繁忙,請重試");
}
3. 消息隊(duì)列
將請求放入消息隊(duì)列,異步處理訂單,避免直接操作數(shù)據(jù)庫。
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$orderData = [
'product_id' => $productId,
'user_id' => $userId,
'quantity' => 1,
];
$redis->lpush('order_queue', json_encode($orderData));
消費(fèi)者處理訂單:
while (true) {
$orderData = $redis->rpop('order_queue');
if ($orderData) {
$order = json_decode($orderData, true);
// 處理訂單邏輯
}
}
4. 限流與降級
通過限流和降級機(jī)制,防止系統(tǒng)過載。
4.1 限流
使用Redis或令牌桶算法限制請求速率。
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$key = 'rate_limit_' . $userId;
$limit = 100; // 每秒允許的請求數(shù)
if ($redis->incr($key) > $limit) {
throw new Exception("請求過于頻繁,請稍后再試");
}
$redis->expire($key, 1);
4.2 降級
在高并發(fā)時,暫時關(guān)閉非核心功能,確保核心業(yè)務(wù)正常運(yùn)行。
if ($isHighTraffic) {
// 關(guān)閉非核心功能
$coreService->processOrder($orderData);
} else {
// 正常處理
$normalService->processOrder($orderData);
}
5. 數(shù)據(jù)庫優(yōu)化
5.1 索引優(yōu)化
確保庫存字段有索引,提升查詢效率。
5.2 分庫分表
通過分庫分表分散數(shù)據(jù)庫壓力。
6. 緩存
使用Redis等緩存庫存信息,減少數(shù)據(jù)庫訪問。
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$stock = $redis->get('product_stock_' . $productId);
if ($stock > 0) {
$redis->decr('product_stock_' . $productId);
// 其他業(yè)務(wù)邏輯
} else {
throw new Exception("庫存不足");
}
總結(jié)
解決PHP高并發(fā)下的超賣和超扣問題,通常需要結(jié)合數(shù)據(jù)庫鎖、分布式鎖、消息隊(duì)列、限流、降級、數(shù)據(jù)庫優(yōu)化和緩存等多種手段,具體方案應(yīng)根據(jù)業(yè)務(wù)需求選擇。