分布式锁——JVM锁、MySQL锁解决多线程下并发争抢资源
分布式锁——JVM锁、MySQL锁解决库存超卖问题
引入库存扣案例
需求背景
电商项目中,用户购买商品后,会对商品的库存进行扣减。
需求实现
根据用户购买商品及商品数量,对商品的库存进行指定数量的扣减
public String deductStock(Long goodsId,Integer count){
// 1.查询商品库存的数量
Integer stock = stockDao.selectStockByGoodsId(goodsId);
// 2.判断库存数量是否足够
if(stock<count){
return "库存不足";
}
//3. 如果库存数量足够,那么就去扣减库存
stockDao.updateStockByGoodsId(goodsId,stock-count);
return "库存扣减成功";
}
使用JMeter进行压测,会产生无法扣减完库存
使用JVM锁
使用Synchronized
public synchronized String deductStock(Long goodsId,Integer count){
// 1.查询商品库存的数量
Integer stock = stockDao.selectStockByGoodsId(goodsId);
// 2.判断库存数量是否足够
if(stock<count){
return "库存不足";
}
//3. 如果库存数量足够,那么就去扣减库存
stockDao.updateStockByGoodsId(goodsId,stock-count);
return "库存扣减成功";
}
使用 ReentrantLock
@Autowired
private StockDao stockDao;
ReentrantLock lock =new ReentrantLock();
public String deductStock(Long goodsId,Integer count){
lock.lock();
try{
// 1.查询商品库存的数量
Integer stock = stockDao.selectStockByGoodsId(goodsId);
// 2.判断库存数量是否足够
if(stock<count){
return "库存不足";
}
//3. 如果库存数量足够,那么就去扣减库存
stockDao.updateStockByGoodsId(goodsId,stock-count);
}finally {
lock.unlock();
}
return "库存扣减成功";
}
JVM失效场景有哪些
-
多例模式下锁会失效
-
事务模式,加锁在事务之前,否则事务扣减库存还未提交,另一个线程又获取到锁。
- 可以将synchronized加到controller。解决该事务模式下问题
- 集群模式:类似与独立模式下,当多个JVM时,无法用Synchronized锁住。
MySQL锁特性解决库存超卖问题
MySQL悲观锁—单条update语句问题
- 易造成锁范围过大。
- 无法在程序中获取扣减库存之前的库存值,比如将之前的库存数量进行打印和发送其他系统
- 很多场景下无法满足其他业务诉求
MySQL forUpdate语句
- 易造成锁范围过大
- 性能较差
- 思索问题
- select for update 和普通 select语句读取内容不一致
MySQL乐观锁
MySQL乐观锁实现——版本号
实现方式
- 给数据库表增加一列“version”
- 读取数据的时候,将“version”字段一并读出
- 数据每更新一次,“version”字段加1
- 提交更新时,判断库中的“version”字段值和之前取出来的“version”比较
- 相同更新,不相同重试
MySQL乐观锁实现——时间戳
实现方式
- 给数据库表增加一列“timestamp”
- 读取数据的时候,将“timestamp”字段一并读出
- 数据每次更新一次,“timestamp”字段取出当前时间戳
- 提交更新时,判断库中的“timestamp”字段值和之前取出来的“timestamp”比较
- 相同更新,不相同重试
举例代码实现
Dao层代码
Service层代码,需要一定的自旋(只有当version值匹配到才终止)
MySQL乐观锁—问题
- 高并发写操作下性能低,适应于读多写少的场景
- 存在ABA问题