PHP高并发下处理超卖和超扣问题

在PHP高并发场景下,解决超卖和超扣问题需要从多个方面入手,以下是一些常见的解决方案:

1. 数据库锁机制

1.1 悲观锁

在事务中通过 SELECT ... FOR UPDATE 锁定记录,确保同一时间只有一个请求能修改数据。

$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 乐观锁

通过版本号或时间戳控制并发,更新时检查版本号是否一致。

$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 命令实现分布式锁,确保同一时间只有一个请求能执行关键操作。

$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);
            // 其他业务逻辑
        } else {
            throw new Exception("库存不足");
        }
    } finally {
        if ($redis->get($lockKey) === $lockValue) {
            $redis->del($lockKey);
        }
    }
} else {
    throw new Exception("系统繁忙,请重试");
}

3. 消息队列

将请求放入消息队列,异步处理订单,避免直接操作数据库。

$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));

消费者处理订单:

while (true) {
    $orderData = $redis->rpop('order_queue');
    if ($orderData) {
        $order = json_decode($orderData, true);
        // 处理订单逻辑
    }
}

4. 限流与降级

通过限流和降级机制,防止系统过载。

4.1 限流

使用Redis或令牌桶算法限制请求速率。

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$key = 'rate_limit_' . $userId;
$limit = 100; // 每秒允许的请求数

if ($redis->incr($key) > $limit) {
    throw new Exception("请求过于频繁,请稍后再试");
}

$redis->expire($key, 1);

4.2 降级

在高并发时,暂时关闭非核心功能,确保核心业务正常运行。

if ($isHighTraffic) {
    // 关闭非核心功能
    $coreService->processOrder($orderData);
} else {
    // 正常处理
    $normalService->processOrder($orderData);
}

5. 数据库优化

5.1 索引优化

确保库存字段有索引,提升查询效率。

5.2 分库分表

通过分库分表分散数据库压力。

6. 缓存

使用Redis等缓存库存信息,减少数据库访问。

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$stock = $redis->get('product_stock_' . $productId);

if ($stock > 0) {
    $redis->decr('product_stock_' . $productId);
    // 其他业务逻辑
} else {
    throw new Exception("库存不足");
}

总结

解决PHP高并发下的超卖和超扣问题,通常需要结合数据库锁、分布式锁、消息队列、限流、降级、数据库优化和缓存等多种手段,具体方案应根据业务需求选择。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容