Redis实现高并发下的抢购,秒杀,解决方案

时间:2022-10-27 18:00:00

在电子商务发达的今天,各种秒杀,抢购活动的场景不少,如何解决高并发下出现的订单超发情况呢?

在常规情况下,我们会根据用户提交的请求去查询商品库存,如果库存小于0则订单生成失败。但是这种情况下经常了订单需要的数量超过了库存数量,即出现负库存。

先讨论常规场景测试:

我们在redis中设置一个库存表,就是个简单的string类型, 用以标示库存即可。 

set inventory 100

使用多进程测试如下:因为我的AB测试不能用。所以使用多进程的方式进行

//register a signal
pcntl_signal(SIGCHLD, SIG_IGN);

$times = 200;
while ($times-- > 0) {
    $pid = pcntl_fork();
    if ($pid > 0) {
    } else {
        order();
        die;
    }
}
sleep(20);
function order() {
    $conn = new Redis;
    //connect redis
    $conn->connect("127.0.0.1", 6379);
    $inventory = $conn->get('inventory');
    //由于本地服务过访问过快。所以休息50毫秒真实模拟高并发
    usleep(50000);
    if ($inventory > 0) {
        $conn->decr("inventory");
    } else {
        echo "抢购失败!";
    }
    die;
}

执行结果变为了负数:

Redis实现高并发下的抢购,秒杀,解决方案


如何解决这个问题呢? 熟悉redis 的同学都知道 redis 支持事务,我们加个事务试试。


<?php


//register a signal
pcntl_signal(SIGCHLD, SIG_IGN);


$times = 200;
while ($times-- > 0) {
    $pid = pcntl_fork();
    if ($pid > 0) {
    } else {
        order();
        die;
    }
}
sleep(20);
function order() {
    $conn = new Redis;
    //connect redis
    $conn->connect("127.0.0.1", 6379);
    $inventory = $conn->get('inventory');
    $conn->multi();
    //由于本地服务过访问过快。所以休息50毫秒真实模拟高并发
    usleep(50000);
    if ($inventory > 0) {
        $conn->decr("inventory");
        $conn->exec();
    } else {
        echo "抢购失败!";
    }
    die;
}


?>

同样结果还是会变成负数。 

Redis实现高并发下的抢购,秒杀,解决方案

接下来可以使用redis 的watch 实现一个乐观锁来试试

<?php

//register a signal
pcntl_signal(SIGCHLD, SIG_IGN);

$times = 200;
while ($times-- > 0) {
    $pid = pcntl_fork();
    if ($pid > 0) {
    } else {
        order();
        die;
    }
}
sleep(20);
function order() {
    $conn = new Redis;
    //connect redis
    $conn->connect("127.0.0.1", 6379);

    do {
        //watch inventory
        $conn->watch('inventory');
        $inventory = $conn->get('inventory');
        //由于本地服务过访问过快。所以休息50毫秒真实模拟高并发
        usleep(50000);
        if ($inventory <= 0) {
            echo "抢购失败!";
            break;
        }
        $conn->multi();
        $conn->decr("inventory");

    } while ($conn->exec());

    die;
}

?>

执行结果:

大致场景就是这样的, 如果是抢购,每次只抢购一个 可以往队列里添加库存个商品的1 队列, 如图:

Redis实现高并发下的抢购,秒杀,解决方案

当用户每次抢购抛出一个队列,然后再去减少真正的库存即可。  当然真实的场景要稍复杂一些, 不过原理就是这样的。 

Redis实现高并发下的抢购,秒杀,解决方案

成功!