秒杀超卖问题,就是有一个商品抢购活动,一个商品假如有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='订单表';
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('库存减少失败');
}
}
}
先访问:/test/index 将redis的list初始化
然后可以访问:/test/order 进行模拟
发表回复