十三、利用分布式锁解决超卖问题

发布时间 2023-05-28 22:08:51作者: 夏雪冬蝉

库存超卖问题

对于商城系统。超卖了一部分可以补获,12306对超卖问题更敏感。

JMeter的使用

 

 超卖演示&使用JMeter对购票功能进行压测

 

 使用synchronized是否能解决库存超卖?

超卖问题出现原因:

 假设余票为1,此时多个线程同时查询到这条余票记录,并进行扣减,那么则会导致超卖发生。

public synchronized void doConfirm(ConfirmOrderDoReq req)

加锁会导致吞吐量/TPS变低,即:售卖效率不高。

一个节点可以加锁,1万个节点呢?还是无法解决超卖。(加节点可以解决性能问题)

存在问题

  • 会导致售卖效率不高(可以容忍)
  • 在多节点的情况下,还是会出现超卖(不能容忍)。
  • 多个人抢多个车次也不应该受影响。

使用Redis分布式锁能否解决库存超卖?

解决超卖:所有节点都能得到的锁

可以通过数据库加锁,性能不高。

所以用redis加锁。

第一个线程进来setIfAbsent设置为true,此时其他线程不可入。

1 String key = req.getDate() + "-" + req.getTrainCode();
2         Boolean setIfAbsent = redisTemplate.opsForValue().setIfAbsent(key, key, 3600, TimeUnit.SECONDS);
3         if (setIfAbsent) {
4             LOG.info("恭喜,抢到锁啦!");
5         } else {
6             LOG.info("很遗憾,没有抢到锁");
7             throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_LOCK_FAIL);
8         }

key就用日期加车次号,value随意。没抢到有提示。

新的问题:

某一处出现异常会怎么样?必须过了超时时间才能进新的请求。

可以在方法结尾删除锁  redisTemplate.delete(lockKey);  

只能解决正常流程问题,异常还有待处理。

将整个set锁以下的部分加异常处理。

1 Boolean setIfAbsent = redisTemplate.opsForValue().setIfAbsent(lockKey, lockKey, 5, TimeUnit.SECONDS);
2         if (setIfAbsent) {
3             LOG.info("恭喜,抢到锁啦!");
4         } else {
5             LOG.info("很遗憾,没有抢到锁");
6             throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_LOCK_FAIL);
7         }

如果这部分也加了锁,没抢到锁也会抛异常,会把别人的锁给删掉。

  • 加锁的动作不要放在try里
  • 加锁时,将当前线程ID放到锁对应的value中,删除时,先去获取value,比对value值和当前线程ID一致时才能删除。

使用Redisson看门狗解决超时问题

之前的方法就是setnx命令。

最关键的问题是有超时时间,如果项目很大,卡住线程,锁释放掉,还会引起超卖问题。

设置守护线程(看门狗),不断重置超时时间。

使用守护线程的好处是会随主线程的结束而结束,所以不会出现一直重置成60s,永不过期的问题。

Redis红锁是什么

看门狗锁还会遇到redis宕机问题。

第1个线程拿到锁,redis宕机。第二个线程进来也会拿到锁。

如果有A B C D E台redis(不是集群),第1个线程进来拿到A B C,拿到半数以上的节点,就认为拿到了锁。要求必须是奇数台redis。

RedissonRedLock。