在商品秒杀活动中,好比商品库存只有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并发更新