关于库存
2023-06-14 本文已影响0人
小王ovo
库存的核心(sql)
update product_skus set stock = stock - #{num} where id = id and stock >= num
或者
update product_skus set stock = stock - #{num} where (stock- #{num}) >= 0
缺点
虽然这句sql使用了乐观锁 stock >= num但是还是会有以下问题.(在mysql默认的rr隔离级别下,update语句会加锁,所以是串行执行的)。行级锁的原因存在性能瓶颈,高并发会出现请求堵塞超时问题。
其他方案(行锁 for update)
解决方案
将库存放入redis 使用INCR(原子自增+1)DECR(原子-1) INCRBY num(可以自定义加减数量) DECRBY
原子修改库存,伪代码如下.
秒杀场景
//判断用户是否已经购买
boolean exist = redisClient.query(productId,userId);
if(exist) {
return -1;
}
//判断库存是否充足,如果扣减之后小于0则库存不足
if(redisClient.incrby(productId, -1)<0) {
return 0;
}
//添加购买记录
redisClient.add(productId,userId);
return 1;
附带一个lua脚本
StringBuilder lua = new StringBuilder();
lua.append("if (redis.call('exists', KEYS[1]) == 1) then");
lua.append(" local stock = tonumber(redis.call('get', KEYS[1]));");
lua.append(" if (stock == -1) then");
lua.append(" return 1;");
lua.append(" end;");
lua.append(" if (stock > 0) then");
lua.append(" redis.call('incrby', KEYS[1], -1);");
lua.append(" return stock;");
lua.append(" end;");
lua.append(" return 0;");
lua.append("end;");
lua.append("return -1;");
非秒杀场景
//判断库存是否充足,如果扣减之后小于0则库存不足
if(redisClient.decrby(productId, num)>=0) {
//正常下单
}else{
//库存不足,将扣减库存恢复至 redis
redisClient.incrby(productId, num)
}
但是这样写两条命令就没有原子性了,在并发情况下一个请求库存不足,还没有恢复,另一个请求本来可以扣减成功,由于第一个请求还没有恢复导致扣减失败.可以使用redis锁或者lua脚本的方式保证两条命令的原子性.
订单取消、订单售后、取消支付等情况回滚库存
这种情况下并发并不高,直接使用数据库悲观锁恢复即可.记得同步redis.
管理后台调整库存,如何防止调整库存时,产生超卖
当作一种特殊的下单处理即可.
关于redis锁(保证加锁和设置失效时间的原子性)
为了防止redis中没有加载库存,大量请求打到数据库上的情况
这种方案下锁的粒度是sku(即单个商品).
1.使用set的方式加锁
String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
if ("OK".equals(result)) {
return true;
}
return false;
lockKey:锁的标识
requestId:请求id
NX:只在键不存在时,才对键进行设置操作。
PX:设置键的过期时间为 millisecond 毫秒。
expireTime:过期时间
- 使用自旋锁提高用户请求的成功率
try {
Long start = System.currentTimeMillis();
while(true) {
String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
if ("OK".equals(result)) {
return true;
}
long time = System.currentTimeMillis() - start;
if (time>=timeout) {
return false;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally{
unlock(lockKey,requestId);
}
return false;
当然可以使用redission更好.
写在最后
1.扣减库存有下单减,支付减,建议下单减,支付减容易产生超卖;
2.在取消订单,支付回调,后台变更库存等边界点容易产生库存数据不准,要注意优化;
3.如果系统并发确实很高,可以考虑限流(令牌桶);
![](https://img.haomeiwen.com/i9821716/771546637509f887.png)