商品超卖问题,你用PHP如何解决?
商场活动秒杀超卖问题,实际上是商品抢购活动,如果一个商品中有100件库存,但是在抢购过程中有1000人来抢购,那么就会出现并发情况, 最初只有100个库存,但要抢购,人数过多,数据库中将只有100个库存,但是当库存为0时也是有人会成功提交。这是超卖问题。
这篇文章是简单的用redis队列来解决超卖问题。redis有list类型,而list类型一个双向链表。我们可以通过push和pop操作从链表的头部或者尾部添加或者删除元素。这么一来list既可以用作栈,也可以用作队列。这里就不对队列做更多的解释。这用redis就解决了并发的问题,在队列里先进先出,有效的解决超卖的问题。
如何设计表?
这里新建三张表: store商品表
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=1 DEFAULT CHARSET=utf8 COMMENT='库存表';
order订单表
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=1 DEFAULT CHARSET=utf8 COMMENT='订单表';
log日志表
CREATE TABLE `log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`event` varchar(255) NOT NULL,
`type` tinyint(4) NOT NULL DEFAULT '0',
`addtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
如何实现呢?
首先将库存循环lpush进入redis值,比如goods_number里去,然后在下单的时候按照次序rpop出来。这样就是下一个单,然后取出来,依次进入队列,等goods_number的值为0时,终止下单。
好了,接下来,我们来看看代码是如何实现的
<?php
namespace HomeController;
use ThinkCacheDriverRedis;
use ThinkController;
class TestController extends Controller {
public function index(){
$wheres = array();
$wheres['goods_id'] = 1;
$number = Store::where($wheres)->get(['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 = 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 = [];
$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=Order::create($data);
//库存减少
$wheres['sku_id'] = $sku_id;
$store_rs=Store::where($wheres)->setDec('number',$number);
if($store_rs){
$this->insertLog('库存减少成功');
}else{
$this->insertLog('库存减少失败');
}
}
}
1、将库存循环存入good_number中 调用index方法
2.然后进行并发下单操作。这里用Apache 的ab测试
运行:
在Windows系统下,打开cmd命令行窗口,定位到apache安装目录的bin目录下 E:phpStudyinpachepache2.4.9in
键入命令:
ab -n 800 -c 800 http://www.startphp.cn/test/order
(-n发出800个请求,-c模拟800并发,请求数要大于或等于并发数。相当800人同时访问,后面是测试url )
执行成功 查看表数据
从表里我们可以看得出来,库存减少到0时,订单也就100个,而log表里也会清晰的记录着。
以上是本文的全部内容,希望对大家学习有帮助,也希望大家多多支持 磊丰的技术博客 感谢阅读!
- 上一篇:程序员择业时常碰到的几个疑惑
- 下一篇:MySQL中你是如何REGEXP正则表达式