走取购项目-防止库存超卖

之前的一篇文章回顾了「走取购」创建订单时库存的扣减时机。今天再来看看项目对于库存超卖的解决办法。

什么是库存“超卖”?其实简单来讲就是多卖了,库存的数量变成了小于0。为什么会出现这种情况,因为明明在扣减库存的时候会去检查剩余数量,然后再进行减法运算。

是的,明面上看这是没什么问题,似乎挺合理的,看下伪代码:

1
2
3
4
5
6
7
8
9
10

public Boolean reduceStocks(String goodsId,int number){
Goods goodsEntity = goodsDAO.findOne(goodsId);
if(goodsEntity.getStock() >= number){
goodsDAO.reduceStock(goodsId,number);
return true;
}else{
return false;
}
}

上面这段简单的代码,如果是放在单线程里面执行,或者不是在高并发的场景下,一般不会发生库存超卖的情况,否则的话,是有大概率使其库存为负数的。

分析一下,试想现实情况下可能会有这样的场景:有2个用户对同一款心仪的商品进行下单购买,但库存有限,仅为1。如果按照上面逻辑执行,运行到查询阶段时,大家看到的库存其实都是一样的,那么在进行“库存数>=购买数”的逻辑判断时结果会为真,接着都执行了更新库存的操作,到这里结果可想而知,库存最后变成了-1,这其实不是我们愿意看到的。

所以先执行查询条件,进而更新库存是走不通的,除非将这个代码块利用synchronized同步锁。对于这种“粗暴”的整块加锁,个人并不推荐,查了资料,在分布式应用下,synchronized的作用范围仅是单个JVM实例。

既然查询不是最新的,其实我们可以先执行符合条件的更新动作,最后再来查询是否库存超卖,将上面的代码改造下:

1
2
3
4
5
6
7
8
9
10
11
12
13

public Boolean reduceStocks(String goodsId,int number){

goodsDAO.reduceStock(goodsId,number);

Goods goodsEntity = goodsDAO.findOne(goodsId);
if(goodsEntity.getStock() >= number){
return true;
}else{
return false;
}

}

可以看到代码的改动量很小,仅是调换了下顺序,便可以防止库存为负的情况,这也是我在项目中实际这样做的,目前暂时没发现什么问题。当然除了这个方法外,网上还有其他的解决办法,比如使用分布式锁,利用数据库的乐观锁和悲观锁等等,达到的效果都是一样的,具体使用的话,就仁者见仁智者见智了。