使用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