06-商品秒杀库存超卖问题
超卖问题
1个商品卖给多个人:1商品多订单。
多人抢购同一商品时,多人同时判断是否有库存,若只剩一个,则都会判断有库存,此时会导致超卖,即一个商品被下了多个订单。
常见库存扣减方式
- 下单减库存:买家下单后,在商品总库存-买家购买数量。这是最简单的减库存方式,也是控制最精确的,下单时直接通过DB事务控制商品库存,一定不会出现超卖的情况。但有些人下完单可能不会付款。
- 付款减库存:买家下单后,不立即减库存,而等到用户付款后才真正减库存,否则库存一直保留给其他买家。但因付款时才减库存,若并发较高,可能导致买家下单后付不了款,因为商品可能已被其他人先付款买走。
- 预扣库存:买家下单后,库存为其保留一定时间(如 30 min),超时候,库存自动释放,其他买家就能继续购买了。买家付款前,系统会校验该订单的库存是否还有保留:若无保留,则再次尝试预扣;若库存不足(即预扣失败)则不允许继续付款;若预扣成功,则完成付款并实际地减去库存。
采用哪种需要基于业务考虑,减库存最核心的是大并发请求时保证DB中的库存字段值不能为负。
方案一:
扣减库存场景,使用行级锁,通过DB引擎本身对记录加锁的控制,保证DB更新的安全性,且通过where语句,保证库存不会被减到负数,也就是能够有效的控制超卖的场景。
update ... set amount = amount - 1 where id = $id and amount - 1 >=0
方案二
设置数据库的字段数据为无符号整数,这样减后库存字段值小于零时 SQL 语句会报错。
2.1 思路分析
利用Redis list队列,给每件商品创建一个独立的商品个数队列,如:A商品有2个,A商品的ID为1001,则创建一个list,key=SeckillGoodsCountList_1001,往该队列中塞2次该商品ID。
每次给用户下单时,先从队列取数据:
-
能取到数据
有库存
-
取不到
无库存
这就防止了超卖。
操作Redis大部分都是先查出数据查,在内存中修改,然后存入Redis。高并发下就有数据错乱问题,为控制数量准确,单独将商品数量整个自增键,自增键是线程安全的,无需担心并发问题。
2.2 代码
每次将商品压入Redis时,创建一个商品队列。
修改SeckillGoodsPushTask,添加一个pushIds方法,用于将指定商品ID放入到指定数字:
/***
* 将商品ID存入到数组中
* @param len:长度
* @param id :值
* @return
*/
public Long[] pushIds(int len,Long id){
Long[] ids = new Long[len];
for (int i = 0; i <ids.length ; i++) {
ids[i]=id;
}
return ids;
}
SeckillGoodsPushTask#loadGoodsPushRedis,添加队列操作:
2.3 防止超卖
修改多线程下单方法,分别修改数量控制,以及售罄后用户抢单排队信息的清理:
FAQ
Q:咋解决超卖?
A:一般解决超卖是在下单时锁库存,若订单取消再释放库存。
Q:发货后再减相应库存,不会超卖吗?
A:下单时需锁定库存,配合发货时再减库存,才不会超卖。
获取更多干货内容,记得关注我哦。