在商品秒殺活動中,好比商品庫存只有100,可是在搶購活動中可能有200人同時搶購,這樣就出現了併發,在100件商品下單完成庫存爲0了還有可能繼續下單成功,就出現了超賣。php
爲了解決這個問題,今天我主要講一下用redis隊列的方式處理。redis有list類型,list類型其實就是一個雙向鏈表。經過lpush,pop操做從鏈表的頭部或者尾部添加刪除元素。這使得list便可以用做棧,也能夠用做隊列。先進先出,一端進,一端出,這就是隊列。在隊列裏前一個走完以後,後一個纔會走,因此redis的隊列能完美的解決超賣併發的問題。html
解決秒殺超賣問題的方法還有好比:1.使用mysql的事務加排他鎖來解決;2.使用文件鎖實現。3.使用redis的setnx來實現鎖機制等。查看點擊:避免商品超賣的4種方案mysql
將商品庫存循環lpush到num裏,而後在下單的時候經過rpop每次取出1件商品,當num的值爲0時,中止下單。web
一共有三張表,分別是:訂單表、商品表、日誌表。redis
1.訂單表sql
CREATE TABLE `ims_order` ( `id` int(11) NOT NULL AUTO_INCREMENT, `order_sn` char(32) NOT NULL, `user_id` int(11) NOT NULL, `status` int(11) NOT NULL DEFAULT '0', `goods_id` int(11) NOT NULL DEFAULT '0', `sku_id` int(11) NOT NULL DEFAULT '0', `number` int(11) NOT NULL, `price` int(10) NOT NULL COMMENT '價格:單位爲分', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5820 DEFAULT CHARSET=utf8 COMMENT='訂單表'
2.商品表json
CREATE TABLE `ims_hotmallstore_goods` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL COMMENT '商品名稱', `type_id` int(11) NOT NULL COMMENT '商品分類', `img` text NOT NULL COMMENT '商品圖片', `money` decimal(10,2) NOT NULL COMMENT '售價', `money2` decimal(10,2) NOT NULL COMMENT '原價', `is_show` int(11) NOT NULL DEFAULT '1' COMMENT '1.上架2.下架', `uniacid` int(11) NOT NULL COMMENT '小程序id', `inventory` int(11) NOT NULL COMMENT '庫存', `details` text NOT NULL COMMENT '詳情', `store_id` int(11) NOT NULL COMMENT '商家id', `sales` int(11) NOT NULL COMMENT '銷量', `logo` varchar(100) NOT NULL, `num` int(11) NOT NULL, `is_gg` int(11) NOT NULL DEFAULT '2' COMMENT '是否開啓規格', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
3.日誌表小程序
CREATE TABLE `ims_order_log` ( `id` int(11) NOT NULL AUTO_INCREMENT, `status` int(11) NOT NULL DEFAULT '0', `msg` text CHARACTER SET utf8, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `status` (`status`) ) ENGINE=InnoDB AUTO_INCREMENT=4562 DEFAULT CHARSET=gb2312 COMMENT='訂單日誌表'
class Test { private static $instance = null; // 用單列模式 實例化Redis public static function Redis() { if (self::$instance == null) { $redis=new \Redis(); $redis->connect('127.0.0.1',6379); self::$instance = $redis; } return self::$instance; } // 將商品庫存循環到lpush的num裏 public function doPageSaveNum() { $redis=self::Redis(); $goods_id=1; $sql="select id, num, money from ims_hotmallstore_goods where id=".$goods_id; $goods=pdo_fetch($sql); if(!empty($goods)){ for($i=1; $i<=$goods['num']; $i++){ $redis->lpush('num',$i); } die('成功!'); }else{ $this->echoMsg(0,'商品不存在。'); } } // 搶購下單 public function doPageGoodsStore() { $goods_id=1; $sql="select id, num, money from ims_hotmallstore_goods where id=".$goods_id; $goods=pdo_fetch($sql); $redis=self::Redis(); $count=$redis->rpop('num');//每次從num取出1 if($count==0){ $this->echoMsg(0,'no num redis'); } $this->doPageGoodsOrder($goods,1); } // 保存日誌 public function echoMsg($status,$msg,$_data="") { $data=json_encode(array('status'=>$status,'msg'=>$msg,'data'=>$_data),JSON_UNESCAPED_UNICODE); $order_log['status']=$status; $order_log['msg']=$msg; $order_log['create_time']=time(); pdo_insert('order_log',$order_log); die($data); } public function orderNo() { return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8); } // 下單更新庫存 public function doPageGoodsOrder($goods,$goods_number) { $orderNo=$this->orderNo(); $number=$goods['num']-$goods_number; if($number<0){ $this->echoMsg(0,'已沒有庫存'); } $user_id=rand(1,500); $order['user_id']=$user_id; $order['goods_id']=$goods['id']; $order['number']=$goods_number; $order['price']=$goods['money']; $order['status']=1; $order['sku_id']=2; $order['order_sn']=$orderNo; $order['create_time']=date('Y-m-d H:i:s'); pdo_insert('order',$order); $sql="update ims_hotmallstore_goods set num=num-".$goods_number." where num>0 and id=".$goods['id']; $res=pdo_query($sql); if(!empty($res)){ $this->echoMsg(1,'庫存扣減成功'.$number); } $redis=self::Redis(); $redis->lpush('num',$goods_number); $this->echoMsg(0,'庫存扣減失敗'.$number); } } // 調用--將商品庫存循環到lpush的num裏 if($_GET['i']==1){ $model = new Test; $model->doPageSaveNum(); } // 調用--高併發搶購下單 if($_GET['i']==2){ $model = new Test; $model->doPageGoodsStore(); }
1.先手動執行:http://127.0.0.1/wqchunjingsvn/web/index.php?i=1
,將商品庫存循環保存到lpush的num裏。服務器
2.這裏我用Apache的ab測試,安裝方法本文最後作補充。打開終端,而後執行:ab -n 1000 -c 200 http://127.0.0.1/wqchunjingsvn/web/index.php?i=2
(-n發出1000個請求,-c模擬200併發,請求數要大於或等於併發數。至關1000人同時訪問,後面是測試url )併發
3.觀察是否執行成功,執行結果以下圖,說明執行成功。
1.查看訂單表,總訂單數量爲100,以下圖,沒問題。
2.查看商品庫存,已經由原來的100變成0,也沒問題。
3.查看日誌表,總共137條記錄,其中status爲1的只有100條,也沒問題。
1.方案可行,庫存爲0,沒有出現超賣。
2.用Apache的ab測試高併發時須要注意Url地址不能拼接上帶&號的參數,不然執行失敗。
php下用redis解決秒殺超賣問題
如何解決高併發秒殺的超賣問題
Mac 安裝Apache http 服務器(用Apache的ab測試,安裝方法)
使用ab進行壓力測試詳解
mysql併發更新