redis实现高并发下的抢购/秒杀功能

  • Post author:
  • Post category:其他


之前对redis高并发下的抢购/秒杀一直是迷迷糊糊的,还是不认真的原因。今天在慕课网学习了一下视频,讲了些基础,也照着他的代码写了案例(有趣的是:免费的课程,老师讲的太简单,甚至还有漏洞):

1:数据库连接(单例模式的):

class Db{
    private static $_instance;
    private  static  $_dbConnect;
    private $_config=array(
        'host'=>'172.22.32.107',
        'user'=>'itop',
        'password'=>'itop',
        'db'=>'test_zd_practice'
    );
    private function  __construct()
    {}
    private  function  __clone()
    {}
     public static function getInstance(){
         if(!self::$_instance instanceof self){
           self::$_instance=new self();
         }
         return self::$_instance;
     }
    public function connection(){
        self::$_dbConnect=mysqli_connect($this->_config['host'],$this->_config['user'],$this->_config['password']);
        if(!self::$_dbConnect){
            throw new Exception("mysql connect error".mysql_error());
        }
        mysqli_set_charset(self::$_dbConnect,'utf8');
        mysqli_select_db(self::$_dbConnect,$this->_config['db']);
        return self::$_dbConnect;
    }
}

2:redis_queue.php

$redis=new Redis();
$redis->connect('127.0.0.1',6379);
$redis_name="order";
$count=10;
for($i=0;$i<100;$i++){
    $uid=rand(10000,99999);
    if($redis->lLen($redis_name)<10){
        $redis->rPush($redis_name,$uid."%".microtime());
        echo $uid."秒杀成功";
    }else{
        echo $uid."秒杀已结束";
    }
}
$redis->close()

3:save_db.php

include 'db.php';
$redis=new Redis();
$redis->connect('127.0.0.1',6379);
$redis_name="order";
while(1){
    $user=$redis->lPop($redis_name);
    if(!$user || $user=='nil'){
        sleep(2);
        continue;
    }
    $arr=explode("%",$user);
    $uid=$arr[0];
    $time_stamp=$arr[1];
    $db=Db::getInstance();
    $conn=$db->connection();
    $result=mysqli_query($conn,"insert into redis_queue(uid,time_stamp) values ($uid,$time_stamp)");
    if(!$result){
        $redis->lPush($redis_name,$user);
    }
    sleep(2);
}
$redis->close();

redis_queue.php  在高并发下是有问题的:


在抢购进行到一定程度,假如现在已经有9个人抢购成功,又来了3个用户同时抢购,这时if条件将会被绕过(条件同时被满足了),这三个用户都能抢购成功。而实际上只剩下一件库存可以抢了。

在高并发下,很多看似不大可能是问题的,都成了实际产生的问题了。要解决“超抢/超卖”的问题,核心在于保证检查库存时的操作是依次执行的,再形象的说就是把“多线程”转成“单线程”。即使有很多用户同时到达,也是一个个检查并给与抢购资格,一旦库存抢尽,后面的用户就无法继续了。

我们需要使用redis的原子操作来实现这个“单线程”。首先我们把库存存在


store


这个列表中,假设有10件库存,就往列表中push10个数,这个数没有实际意义,仅仅只是代表一件库存。抢购开始后,每到来一个用户,就从


store


中pop一个数,表示用户抢购成功。当列表为空时,表示已经被抢光了。因为列表的pop操作是原子的,即使有很多用户同时到达,也是依次执行的。抢购的示例代码如下

$redis=new Redis();
$redis->connect('127.0.0.1',6379);
$store="store";
$order="order";
/*到“/抢购的时间到了”可放入其他地方,抢购时间一般是固定的,可在抢购之前就执行这段代码 */
$count=10;
$len = $redis->lLen($store);
$count = $num - $len;
for ($i = 0; $i < $count; $i++) {
    $redis->lpush($store, 1);
}
//抢购的时间到了
for($i=0;$i<100;$i++) {
    $uid = rand(10000, 99999);
    $count = $redis->lpop($store);    /* 模拟抢购操作,抢购前判断redis队列库存量 */
    if (!$count){
        echo  '秒杀已结束';
    }else{
        $result = $redis->lpush($order,$uid."%".microtime());
        if($result){
            echo '秒杀成功';
        }
    }
}
$redis->close();

本文涉及部分转载,参照:

https://www.cnblogs.com/phpper/p/7085663.html

里面写得很好哦!



版权声明:本文为oJingZhiYuan12原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。