使用redis防止抢购商品超卖


前言:

  • redis不仅仅是单纯的缓存,它还有一些特殊的功能,在一些特殊场景上很好用。
  • 本篇博文用来测试下使用redis来防止抢购商品超卖问题。

内容:

  • 使用redis的list进行测试

    思路是设置一个redis列表List,假设有十个商品,每次请求先判断List的长度,小于十就能抢到商品,将用户信息存放到List中。代码如下

复制代码
 //进行抢购
    protected function way_list(){
        $num = $this->redis->lLen();
        if($this->redis->lLen()>=self::AMOUNTLIMIT){
            $this->writeLog("抢购失败".$num);
            return;
        }else{
            $this->redis->rPush($num);
            $this->writeLog("抢购成功".$num);
        }
    }
复制代码

结果:失败!

可以很明显数量不对顺序也不对。

分析了下原因,在代码执行时,多用户并发请求时,第一个用户判断List长度符合条件还未进行List写入时,第二个用户也通过了List长度判断。所以就导致执行失败。

这就没有利用到redis的原子性

所以进行了改良

  • 使用redis 的incrby。incrby将制定key 的值增加指定的增量,并返回增量后的值。是一个原子性操作。所谓的原子性操作就是执行该方法后要嘛成功要嘛失败。

思路就是设置一个键值对存放被抢购数量,每次一个用户进来就将该值加一进行判断,如果小于抢购的商品数量则抢购成功,否则失败。代码如下

复制代码
 protected function way_string(){
        //判断是否有初始化
        if(!$this->redis->exists(self::sold_name)){
            $this->redis->setnx(self::sold_name,0);
        }
        if($this->redis->incrby(self::sold_name,1) > self::AMOUNTLIMIT){
            $this->writeLog('失败');
        }else{
            $this->writeLog('成功');
        }
    }
复制代码

结果

压力测试了几次都没有出现问题

通过apache自带的ab压力测试,进行五百次连接请求,并发三百次,没有出现超卖行为。

总结:会出现超卖主要是由于用户在请求的时候,代码在执行是有先后,会导致执行结果不符合预期。而采用redis的原子性就能避免。

 

 

 

-----------------------------------------------------------秒杀--------------------------------------------------------------------

 

/*
         * 1.把用户的请求接受,写入redis中的程序
         * 2.把redis中的数据处理,保存在数据库中的程序
         * **/

        // 步骤一:
        public function userMiao()
        {
            //redis连接
            $redis = new \Redis();
            $redis->connect('118.xx.xxx.213','6379');
            $redis->auth('yehui');
            $redis->select(14);
            //列表名
            $redis_name = 'miaosha';
            //模拟大量用户去请求  --只能模拟高压力,不能模拟高并发的
            for ($i=0;$i<20;$i++){
                $uid = rand(1000,9999);
                //接受用户id
               // $uid = $_GET['uid'];
                //获取一下redis里面已有的数量
                $num = 10;
                //如果当天人数少于十的时候,则加入这个队列,
                if($redis->lLen($redis_name) < $num){
                    $redis->rPush($redis_name,$uid.'%'.microtime());
                    echo '用户id:'.$uid.' 秒杀成功 '.'<br/>';
                }else{
                    //如果当天人数已经到达了十个人,则返回秒杀已完成
                    echo "秒杀已经结束".'<br/>';
                }
            }
            //$redis->close();
        }


        //步骤二:
        public function saveMiao()
        {
            //redis连接
            $redis = new \Redis();
            $redis->connect('118.xx.xx5.xx','6379');
            $redis->auth('yehui');
            $redis->select(14);
            //列表名
            $redis_name = 'miaosha';

            //死循环
            //从队列最左侧取出一个值来
            //然后判断这个值是否存在
            //切割出时间,uid
            //保存到数据库中
            //数据库插入的失败的时候的回滚机制

            //死循环
            while (1){
                //从队列最左侧取出一个值来
                $user = $redis->lPop($redis_name);
                //然后判断这个值是否存在
                if(!$user || $user == ''){
                    sleep(2);
                    continue;
                }
                //切割出时间,uid
                $user_arr = explode('%',$user);
                $insert_data = [
                    'uid'=>$user_arr[0],
                    'time_stamp'=>$user_arr[1],
                ];
                //保存到数据库中
                $res = Db::name('redis_queue')->insert($insert_data);
                //数据库插入的失败的时候的回滚机制
                if(!$res){
                    $redis->rPush($redis_name,$user);
                }
                sleep(2);
                echo $user_arr[0].'储存秒杀';
            }
            

        }

 

 

 

 

 

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM