通过本节能学到什么?
什么是抢购的超卖现象?
常见的解决思路有哪些?最好的解决思路是什么?
1、引出问题:
在做抢购系统的时候,我们首先应该想到的是怎么才能避免超卖,比如说:库存只有100,结果卖了200。这种情况肯定是不能接受的。如果我们不在代码中针对该问题进行相应处理,很有可能就会造成这种不良后果。
比如:
MySQL,Redis常见错误处理方式,下面用伪代码来展示下:
# 查询数据库存放的库存
$goods_num = $this->goods_db->get_goods_num($goods_id);
# 判断库存
if($goods_num >=1)
{
# 用户和该订单绑定等处理
# 减库存
}
?>
如上就是我们最常见的处理方式。我们来分析下上面这段代码是怎么造成超卖问题的。当剩余库存为1 ,同时有两名用户请求数据。此时他们同时走到第6行代码,因为此时他们从第3行代码查询到的库存都是1,所以都进入到了if语句里面。都进行了减库存处理,导致库存变成了-1。
2、怎么解决?
首先我们要明确解决该问题的核心原则:于所有的用户对于库存的读写操作必须是串行的!
举例理解:
并行:多位用户同时检查库存,发现有剩余之后同时减库存。如上面的伪代码就是并行。
串行:用户1检查库存--有剩余之后减库存--用户2检查库存--有剩余之后减库存--用户3检查库存--有剩余之后减库存...... 如果回归到上面的伪代码上,就意味着上面的伪代码要执行的话,必须一个人接一个人的执行,不能多人同时执行。
上面已经有了“天上飞的”,下面相应的就要有“地上跑的”
方案:
方案一:Mysql中可以应用悲观锁和乐观锁。
因为该方式不是最好的实现方式,不再展开去讲,可以参考该文章了解下。
方案二(最好):Redis的消息队列。
还是用php代码来处理下:
#########################后台创建商品侧################################################
# 在创建商品库存的时候,同时创建一个该商品的redis list key ,来存放该商品所有的库存。
$goods_num = 100;
$goods_id = 1;
$goods_list_key = 'goods_num:'.$goods_id;
for($i=0; $i
{
$Redis->Lpush($goods_list_key,1);
}
# 经过上面一番操作,$goods_list_key已经存放了100个元素,每个元素都是1
#########################################################################
......
#########################前台用户抢购商品侧###############################################
$goods_id = 1;
$goods_list_key = 'goods_num:'.$goods_id;
$user_get_goods = $Redis->Lpop($goods_list_key);
if(!empty($user_get_goods)))
{
# 用户和商品建立绑定关系
# 减库存(到MySQL或者Redis减库存都行,不过一般这种落地数据都会采用放到MySQL中)
}
#########################################################################
?>
下面来分析下,上面这段代码是如何避免掉超卖问题的。
当商品的库存只剩下1的时候,此时有两位用户同时来取商品。第一个用户把库存取走(pop)之后;第二个用户再来取库存(pop)的时候,发现什么也没取到,那么就直接返回就行,也不需要再去减库存。
拓展:
注意实际项目中一般稍微复杂一点,还有加一条逻辑:一位用户只能取一个商品,多次来的请求并不能多次取商品。这条也非常常见的要求,毕竟万一有用户来刷,没有限制的话,所有商品可能都被该用户取走。
思路:只需要在进行领取之前查询下用户是否已经领取过即可
实际场景:
最近做了一个需求,虽然不是抢购,但是也是有库存限制。由于没有针对超卖问题进行处理,就出现了库存为-1的情况。如上面优化之后就没有问题了。
参考文档: