這篇文章主要介紹了關於php結合redis 秒殺商品的詳解,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下
1.1建立商品表,訂單表,並初始化資料
訂單表。
1.2 將商品資料寫入redis 佇列。
例如編號1 商品有100件。 就往 goods_1 佇列寫100個1 進去。例用pop 操作的原子性(扛並發) 後面購買時,買一個就pop 一個。
//代码使用yii 框架,重点在思路,其它框架做少量调整即可。 $redis = self::createRedisObj(); //创建redis 对象,后面提供详细代码 $sql = "select * from sec_goods"; $rows = Yii::app()->db->createCommand($sql)->queryAll(); foreach( $rows as $key => $row ): $goods_id = $row["id"]; $stock_avail = $row["stock_avail"]; $redis_key = "goods_".$goods_id; for($i =0 ; $i< $stock_avail; $i++){ $redis->lpush($redis_key , 1); } echo $goods_id."llen is ".$redis->lLen($redis_key)."<br/>"; endforeach;
建好後如下圖。 (真實情況下,後台可能出現庫調整,需要對應的去同步redis 中的數據,實際項目中請留意,此處暫且不表)
//用随机值模拟客户,商品,单次购买份数 $uid = rand(1,10); $amount = rand(1,5); $goods_id = rand(1,6); $time = time(); $this->buy($uid , $goods_id , $amount); public function buy($uid , $goods_id , $amount){ //使用行锁. try { $trans = Yii::app()->db->beginTransaction(); $sql = "select stock_avail from sec_goods where id = $goods_id for update"; // $stock_avail = Yii::app()->db->createCommand($sql)->queryScalar(); if( $stock_avail >= $amount ){ //份额足够。 $sn = date("YmdHis")."-".$uid."-".$goods_id.rand(1000,9999); $sql = "insert into sec_order set sn = '$sn',user_id = $uid, goods_id = $goods_id, create_at = $time,num = $amount"; $bool = Yii::app()->db->createCommand($sql)->execute(); if( !$bool ){ throw new Exception("执行失败".$sql); } $sql = "update sec_goods set stock_avail = stock_avail - $amount where id= $goods_id"; $bool = Yii::app()->db->createCommand($sql)->execute(); if( !$bool ){ throw new Exception("执行失败".$sql); } } $trans->commit(); return true; } catch (Exception $e) { //日志记录 $trans->rollback(); return false; } }
然後使用 apache 的 ab 小工具進行測試。
-n 代表請求次數 -c 代表單次並發多少個請求。
ab -n 1000 -c 100 http://xxx
(上述程式碼中的事務去掉,再ab 跑時會出現爆單,超賣問題 詳情點擊)
由於使用了行鎖定for update。基於事務的隔離性,一定是順序的執行,所以上述程式碼,也不會有超賣爆單問題。 (10件庫存賣出11件),但這樣的程式碼,有個性能問題,就是有多少次並發請求,就會往資料庫請求多少次。脆弱的mysql 很快就崩掉了。
終於上正菜了。 。 。 。
//code 3.1 //用随机值模拟客户,商品,单次购买份数 $uid = rand(1,10); $amount = rand(1,5); $goods_id = rand(1,6); $time = time(); //用redis 校验,此次用户是否可以买。(库存是否充足) $redis = self::createRedisObj(); $redis_key = "goods_".$goods_id; $len = $redis->lLen($redis_key); //求队列的长度,也就是商品的库存。 if( $len == 0 ){ exit("抢光了!"); } else if( $len < $amount){ exit("库存不足!"); } //验证通过,开始pop 出队列。 pop 一个,相当于买一个。 for( $i =0 ; $i< $amount;$i++){ $redis->rPop( $redis_key ); } $bool = $this->buy($uid , $goods_id , $amount); if( !$bool ) { //如果购买失败,则把取出的redis 队列的数据,再压回去。(回充库存) for( $i =0 ; $i< $amount;$i++){ $redis->lPush( $redis_key , 1); } } //创建redis 对象的。 private static $_redis = null; /** * 创建一个redis 对象. * @return Redis */ public static function createRedisObj(){ //2017-11-29 改为单例创建模式. if( ! self::$_redis){ $redis = new Redis(); $host = “192.168.1.xx”; $port = "6379"; $redis->connect($host,$port); self::$_redis = $redis; } return self::$_redis; }
注意一個小細節,通常redis 會結合框架做快取。 上述例子中,請注意在創redis 物件時,再單獨指定一個函式庫。 (redis 一般有9個可選),避免伺服器清除快取時將資料清空。
致此,上述程式碼完成了一個基礎版本。
----------------------------------------------- -------------------------------------------------
然而在現像中,會隨營運需求不斷的產生而改變。
隨便舉一例。
1 產品希望單一用戶3秒內只能買一次。無商品
2 產品希望單一用戶單一商品最多限買5 件。
碰到這種情況
問題1 處理如下
//用随机值模拟客户,商品,单次购买份数 $uid = rand(1,10); $amount = rand(1,5); $goods_id = rand(1,6); $time = time(); $redis = self::createRedisObj(); ////////////单用户限3秒内仅允许请求一次/////////////////////////////// $lock_key = "uTimeLimit_".$uid; //按用户名编即可。 如果限用户针对指定商品,则lock_key 按uid+ goods_id 进行唯一编码 if( $redis->get($lock_key)){ $left_time = $redis->ttl($lock_key); exit($expire ."秒内只允许 $tag 一次!请".$left_time."之后再尝试"); }else { $redis->setEx($lock_key , $expire , "1" ); } ////////////////////////////////////////////////////////////////// //用redis 校验,此次用户是否可以买。(库存是否充足) $len = $redis->lLen($redis_key); //求队列的长度,也就是商品的库存。 if( $len == 0 ){ exit("抢光了!"); }else if( $len < $amount){ exit("库存不足!"); }
// 後序購買流程。 。 。 。 。
問題2 修改如下。
//用随机值模拟客户,商品,单次购买份数 $uid = rand(1,10); $amount = rand(1,5); $goods_id = rand(1,6); $time = time(); $redis = self::createRedisObj(); ////////////单用户限5个处理/////////////////////////////// //设一个hash 表, "user_buy" 然后 $uid . "_" . $goods_id 来记录购买的份数。 $already_num = $redis->hGet("user_buy",$uid."_".$goods_id)? $redis->hGet("user_buy",$uid."_".$goods_id) :0; //求出已购买份额 if( $already_num +$amount > 5){ exit("单用户单个商品限购买5个");} else{ $new_num = $already_num +$amount ; $redis->hSet("user_buy",$uid."_".$goods_id , $new_num); } ////////////////////////////////////////////////////////////////////用redis 校验,此次用户是否可以买。(库存是否充足) $len = $redis->lLen($redis_key); //求队列的长度,也就是商品的库存。 if( $len == 0 ){ exit("抢光了!"); } else if( $len < $amount){ exit("库存不足!"); }// 后序购买流程。。。。。 //如果购买失败 $redis->hSet( "user_buy",$uid."_".$goods_id , $redis->hGet("user_buy",$uid."_".$goods_id ) - $amount //失败时,则回复hash 表的数值 );
這兩個問題處理完畢,其它類似問題見招拆招。 有心的讀者可以發現,這類修改有共同點,可以適當地封裝下程式碼,更好的複用。
相關推薦:
PHP結合Redis來限制使用者或IP某個時間段內造訪的次數
#
以上是php結合redis 秒殺商品的詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!