PHP高并發(fā)下處理超賣和超扣問題

在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ù)需求選擇。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容