秒杀超卖问题,就是有一个商品抢购活动,一个商品假如有100件库存,但是在抢购时有200人来抢购,这时就会并发,原本只有100的库存但是抢购的人过多,就会发生数据库里原本只有100的库存但是库存为0的时候还会有人提交成功,这就是超卖。

  今天简单的用redis的队列来解决超卖问题。因为redis有list类型,list类型其实就是一个双向链表。通过push,pop操作从链表的头部或者尾部添加删除元素。这使得list既可以用作栈,也可以用作队列。先进先出  一端进  一端出这就是队列。这里用redis就解决了并发的问题,在队列里前一个走完之后,后一个才会走。

   将库存循环lpush进一个redis值goods_number里去,然后在下单的时候依次rpop出来。这样就是下一个单取出来一个,然后等goods_number的值为0时,停止下单。

  首先建立三个表 store商品表  order订单表  log日志表

CREATE TABLE `store` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `goods_id` int(11) NOT NULL,
   `sku_id` int(10) unsigned NOT NULL DEFAULT '0',
   `number` int(10) NOT NULL DEFAULT '0',
   `freez` float(11,2) NOT NULL DEFAULT '0.00' COMMENT '虚拟库存',
   `price` int(10) NOT NULL COMMENT '价格:单位为分',
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='库存';
CREATE TABLE `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=4743 DEFAULT CHARSET=utf8 COMMENT='订单表';

测试代码如下:

<?php
 namespace Home\Controller;
 use Think\Cache\Driver\Redis;
 use Think\Controller;
  
 class TestController extends Controller {
     public function index(){
     	$wheres = array();
     	$wheres['goods_id'] = 1;
     	$number = M('store')->where($wheres)->getField('number');
         $redis = new Redis(); 
 		for($i=0;$i<$number;$i++){  
 		    $redis->lpush('goods_number',1);  
 		}  
 		echo $redis->llen('goods_number');  
     }
  
     //生成唯一订单号  
 	function build_order_no(){  
 	    return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);  
 	}  
 	//记录日志  
 	function insertLog($event,$type=0){
         $data['event'] = $event;
         $data['type']  = $type;
 	    $res = M('log')->add($data);
 	}  
 	  
 	//模拟下单操作  
 	//下单前判断redis队列库存量  
 	function order(){
         $sku_id  = 11;  //传入已知的sku_id;
     
         $wheres = array();
     	$wheres['sku_id'] = $sku_id;
     	$good_info = M('store')->where($wheres)->find();
  
 		$user_id  = rand(1,200);
 		$goods_id = $good_info['goods_id'];
 		$price    = $good_info['price'];
 		$number   = 1;//抢购时每次买一件商品
  
  
 		$redis=new Redis();
 		$count=$redis->rpop('goods_number');  //下单时做rpop 从goods_number中取出1
 		if($count == 0){  
 		    $this->insertLog('error:no goods_number redis');  
 		    return;  
 		}
  
  
         if( ($good_info['number'] - $number) <= 0){
             $this->insertLog('商品售罄');  //如果库存为0写入日志 并停止下单操作
             return;
         }
 		 
 		
 		//生成订单    
 		$order_sn=$this->build_order_no();
  
         $data = array();
         $data['order_sn'] = $order_sn;
         $data['user_id']  = $user_id;
         $data['goods_id'] = $goods_id;
         $data['sku_id']   = $sku_id;
         $data['number']   = $number;
         $data['price']    = $price;
  
 		$order_rs=M('order')->add($data);
 		  
 		//库存减少  
 		$wheres['sku_id'] = $sku_id;
 		$store_rs=M('store')->where($wheres)->setDec('number',$number);
 		if($store_rs){
 		    $this->insertLog('库存减少成功');  
 		}else{    
 		    $this->insertLog('库存减少失败');  
 		}
 	}
 	
 }

 

1、将库存循环存入good_number中   调用index方法

php下用redis解决秒杀超卖问题_apache

2.然后进行并发下单操作。这里用Apache 的ab测试

运行: 
在Windows系统下,打开cmd命令行窗口,定位到apache安装目录的bin目录下 F:\wamp\wamp\bin\apache\apache2.4.9\bin

键入命令: 
ab -n 800 -c 800   http://www.ceshi.com/test/order
(-n发出800个请求,-c模拟800并发,请求数要大于或等于并发数。相当800人同时访问,后面是测试url )

php下用redis解决秒杀超卖问题_redis_02

执行成功 查看表数据

php下用redis解决秒杀超卖问题_Redis_03

php下用redis解决秒杀超卖问题_redis_04

php下用redis解决秒杀超卖问题_apache_05

 库存为0,订单有100个。没有超卖!