【Redis】解决Redis分布式锁误删问题

一、思路

刚刚我们已经分析了redis分布式锁初级版本存在的问题以及对应的解决思路,其实关键点就是两个:

1、在获取锁时存入线程标示

2、在释放锁时先获取锁中的线程标示,判断是否与当前线程标示一致

从而避免误删别人的锁。

1653387398820

不过这里建议存入锁的时候使用的是UUID。

之前使用的是线程的id,线程id是一个递增的数字,JVM内部每创建一个线程,它的数字就会递增。但是如果我们是在集群的模式下,我们有多个JVM,这样一来,每个JVM内部都会维护这样一个递增的数字,那两个JVM很有可能出现线程id冲突的情况,所以说我们直接使用线程的id去作为线程标识是不够的,我们还要去区分不同的JVM,让它们产生一些差异,因此这里使用UUID。

事实上我们完全可以在去创建锁的时候整一个UUID,那这个锁的内部每来一个线程,我们再把线程ID拼接到后面,两者结合,这样用UUID来区分不同的JVM,再用线程ID来区分不同线程,两者结合,就能够去确保不同线程标识一定不一样,相同线程标识一定一样。

  • 如果一致则释放锁
  • 如果不一致则不释放锁

总结:在存入锁时,放入自己线程的标识,在删除锁时,判断当前这把锁的标识是不是自己存入的,如果是,则进行删除,如果不是,则不进行删除。


二、代码实现

获取锁

// 这里使用hutool工具类中的UUID,因为它里面有toString方法,里面传入true,可以将UUID里面的横线去掉
private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";
@Override
public boolean tryLock(long timeoutSec) {
   // 获取线程标示
   String threadId = ID_PREFIX + Thread.currentThread().getId();
   // 获取锁
   Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
   return Boolean.TRUE.equals(success);
}

释放锁

public void unlock() {
    // 获取线程标示
    String threadId = ID_PREFIX + Thread.currentThread().getId();
    // 获取锁中的标示
    String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
    // 判断标示是否一致
    if(threadId.equals(id)) {
        // 释放锁
        stringRedisTemplate.delete(KEY_PREFIX + name);
    }
}

三、测试

VoucherOrderServiceImpl$seckillVoucher

在释放锁和获取锁的地方分别打个断点

image-20240528174744084

然后Debug运行项目

将数据库中的库存改为100,然后清空所有订单

image-20240528175259542

最后发起请求,同样也是法两个请求

image-20240528175357820

回到IDEA中,跟之前一样,同样发了两个请求过来了。

现在我们要模拟的是其中一个超时了,然后我们去误删的情况,因此我们先让第一个去获取锁,可以发现它现在是获取锁成功的

image-20240528175524553

回到redis中看一下,可以发现是有一把锁的,锁里面记录的就是UUID + 线程id的标识。

image-20240528180317894

现在假设这把锁过期了,这里我们手动删一下,模拟它过期的这种情况

image-20240528180339894

现在过期后,回到IDEA,现在再让 8082 放行,此时它来获取锁,因为之前的锁已经过期了,因此 8082 获取锁是成功的

image-20240528175936202

接下来放行 8081,此时它就会去释放锁的时候我们跟进去,进去后,8081 会获取自己的线程标识。

image-20240528180106869

可以到redis中看一下,可以发现线程标识已经变了

image-20240528180430216

接下来执行代码,它先得到自己的标识,然后得到锁中的标识,可以发现完全不一样

image-20240528180517226

此时8081就知道了,这个锁不是我的,因此此时锁并没有被误删。

将8081放行,回到redis中,可以发现锁还在。

但是如果让8082执行释放锁的逻辑的时候,此时就可以真正释放锁了。

并且回到redis中访问,锁确实也不存在了。

image-20240528180727586

所以我们通过添加线程标识,然后判断线程标识这样的一种机制,就已经解决了误删的问题,让我们的分布式锁变得更加的健壮了。

但是我们现在的分布式锁并不是一个完美的分布式锁,它还存在一些问题,具体什么问题下节继续分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值