扣减时机
何时扣减扣减库存,目前有两种主流方式:
方式1:下单减库存——即用户下单成功时减少库存数量
- 优点:用户体验好,系统逻辑简单;
- 缺点:会导致恶意下单或下单后却不买,使得真正有需求的用户无法购买,影响真实销量;
解决办法:
- 设置订单有效时间,若订单创建成功N分钟不付款,则订单取消,库存回滚;
- 限购,用各种条件来限制买家的购买件数,比如一个账号、一个ip,只能买一件;
- 风控,从技术角度进行判断,屏蔽恶意账号,禁止恶意账号购买;
方式2:付款减库存——即用户支付完成并反馈给平台后再减少库存数量
- 优点:可以有效的避免无效订单;
- 缺点:可能超卖,同一时间支付成功的用户可能超过商品库存;
解决办法:
- 付款前二次校验库存,如确认订单要付款时再验证一次,并友好提示用户库存不足;
- 增加提示信息:在商品详情页,订单步骤页面提示不及时付款,不能保证有库存等。
应用场景
对库存敏感的场景,比如秒杀/抢购/促销等场景建议使用下单减库存,原因是:
- 下单扣减库存逻辑简单,性能高,适用于对秒杀等对要求高性能的场景;
- 这些场景下绝大部分人是有购买意向,即下单成功后会支付;
- 用户体验好,特别是购买人数远超商品库存的场景;
对库存不敏感的场景,建议使用付款减库存,原因是:既没有无效订单,也不用担心超卖;
基本准则
- 防止重复扣减:同一个请求最多被扣减一次;
- 防止超卖:库存不能出现负数;
- 高性能:满足秒杀、抢购等业务需求;
技术方案
方案1:数据库事务
方案思路: 一个事务内,先查询库存量,如果库存量足够,则扣减库存量;
优缺点: 可以防止超卖,但是利用数据库事务容易死锁,性能不高;
------ 事务开始 ------
//查询库存量
select num(库存量) from stock where sid=$sid;
//扣减库存
if(num >= reduce购买数量){
update stock set num=num-$reduce where sid=$sid;
}
------ 事务结束 ------
方案2:数据库CAS乐观锁
方案思路: 业务系统首先先查询库存量,如果库存量足够,以库存量作为版本号扣减库存量;
优缺点: 可以防止超卖,扣减库存的性能取决于数据库,数据库性能成为瓶颈;
方案3:分布式缓存
方案思路: 与方案1思路一致,只不过库存扣减动作放在内存中进行;
优缺点: 性能比与方案1高,但是库存存在于内存中,存在丢失的风险;
方案4:数据库CAS + 缓存
方案思路: 库存查询放在缓存中,所有的读以及超出库存的写请求都被过滤,数据库处理真正在库存容量内的那些请求,然后利用CAS乐观锁扣减库存;
优缺点: 性能比与方案2相比,数据库压力变小,单仍存在单点性能瓶颈;
方案5:库存分段
方案思路: 库存查询和库存扣减都在分布式缓存中进行,然后按照设定的规则对商品库存划分,比如均分或者按照区域分等,扣减库存时按照路由规则路由到对应的分段库存,然后扣减库存,同时异步任务的形式持久化到DB;
优缺点: 库存分段后写性能可水平扩展,真实的库存数据都在分布式缓存中,有数据丢失的风险;
思路总结
- 对于库存查询,可以将库存放在缓存系统中,提升读性能;
- 对于库存扣减,可以考虑使用缓存系统和数据库。像商品库存有限,交易时间短的场景,比如秒杀可以在缓存系统中实现;对于扣减逻辑复杂,需要使用事务等数据库特性的,则在DB中实现;
- CAS机制本质是乐观锁,在写高并发的场景下,冲突/失败的概率变大,应用系统需要不断重试,吞吐量变低;
参考: