在用户订购权益、订购不可叠加的流量包等应用场景下,需求方经常会提出同一个商品只能向同一个用户销售一次,且库存不能小于0(即不能发生超卖),而超卖是开发中经常遇到的问题,本文通过实验将超卖问题复现,并给出超卖的一种解决方案,经验证有效。
1、复现
看下列代码,逻辑上无问题。
if (user == null){
return Result.error(CodeMsg.SESSION_ERROR);
}
// 用程序控制,一个用户只能买一个
Order order = orderService.getOrderByUserIdGoodsId(user.getId(), goodsid);
if(order != null){
return Result.error(CodeMsg.ORDER_REPEAT);
}
// 判断库存
GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(goodsid);
// 如果库存没有了,则购买失败
int stock = goodsVo.getStockCount();
if (stock <= 0){
return Result.error(CodeMsg.ORDER_OVER);
}
// 减库存、下订单、写入订单,用事务写(一步成功则都成功,否则都失败)
OrderInfo orderInfo = orderService.order(user, goodsVo);

压测前,数据库库存表显示goods_id为1的商品库存还有10个。

对购买商品接口进行压测:
(1)首先用脚本构建1000个用户数据,通过访问网站注入到token中、生成到数据库中,同时生成txt用户数据文本。
(2)通过JMeter录制压测脚本,将用户数据txt文本中每行的用户信息做为参数,将购买商品接口路径配置好。

1000个用户同时对goods_id为1的商品进行购买。

压测之后,数据库库存表显示,goods_id为1的商品库存出现负值。

2、解决
解决超卖:数据库语句优化,更新商品数量时加库存数量判断count>0,此原理是根据执行数据库update时,数据库会自动加一个锁,同一时刻只能有一个线程访问。
"update goods set stock_count = stock_count -1 where goods_id = #{goodsId} and stock_count > 0"
同样条件下,进行1000个用户购买同一个商品的并发压测验证,压测之后数据库库存数量为0,超卖问题解决。

顺便解决掉用户可能出现的超买问题:根据场景不同在用户和商品购买关系表中建立唯一索引,比如一个用户对应一个商品包,userid、goodsid建立唯一索引,这样能保证一个用户只能进行一次同一goodsid商品的购买。
以上是通过数据库的锁机制解决掉的超卖与超买问题,另外,前端通过下发短信验证码方式也可以在一定程度上尽量避免因网络抖动问题导致的用户超买。
当然,实际工程中还需用Redis预加载库存数、MQ队列操作数据库的方式来提高下订单、购买商品的效率。
本文聚焦用户订购场景中的超卖问题,通过JMeter对购买商品接口压测复现了超卖现象,即商品库存出现负值。提出数据库语句优化加库存数量判断解决超卖,建立唯一索引解决超买,前端下发验证码避免超买,还提及用Redis和MQ提高效率。
12万+

被折叠的 条评论
为什么被折叠?



