redis实现分布式锁思路及redission分布式锁主流程分析

  • Post author:
  • Post category:其他


1.redis实现分布式锁思路

首先,我们来看一段问题比较明显的代码。

     /**
     * 存在并发问题
     * @param productId
     * @return
     */
    private String reduceStock1(String productId) {
        Integer stock = Integer.parseInt(redisTemplate.opsForValue().get(productId));

        if(stock>0){
            int realStock = stock -1;
            redisTemplate.opsForValue().set(productId,realStock+"");
            System.out.println("扣减成功,剩余库存"+realStock);
        }else {
            System.out.println("扣减失败,库存不足");
        }

        return "";
    }

对于临界资源库存,我们明显有着并发问题,对于集群来说单体锁synchronized或者reentrantLock均不合适。

目前对于分布式锁来说有两种比较流行的解决办法,一种是利用redis,另一种是用zk,我们使用redis来看一下怎么实现。


  • version 1

我们利用setnx的特性进行加锁(第一个红框),此时还非常粗糙,如果有用户获取锁成功但在释放锁之前(第二个红框)出现问题,则会产生死锁。


  • version 2

用finally来保证代码即使有什么问题能够最终释放锁。但是如果服务器宕机了或者重启了怎么办,这时候就需要设置超时时间来保证锁不会被挂掉的线程一直占用。


  • version 3

此时我们设置10s的锁时间,这个时候如果业务逻辑超过10s是不是其他的线程就有可能拿到锁并删除我们的锁。

此时我们要保证谁加的锁谁才能解锁。


  • version 4

这个时候就相对较为完整了。但是还有一个致命的问题,就是锁续命的问题,来保证我们在10s后能够继续生效,使finally里能够获取锁。这就需要再来启动一个线程用expire来给锁续命,并且保证定时执行。

2.redisson分布式锁实现

首先,我们来看一下redisson上锁的实现部分。

在红框中就是上锁的redis命令,这个部分是用lua脚本完成的,用来保证操作的原子性。

KEYS[1] --> Collections.<Object>singletonList(getName())
ARGV[1] --> internalLockLeaseTime
ARGV[2] --> getLockName(threadId)

lua脚本的参数对应如上。

我们来看第一部分if,对应逻辑如下(下面的if为锁重入部分暂不讨论)。

  1. 锁对应的key是否存在。
  2. 如不存在则设置hash值。第一个key为我们传入的锁名,第二个key为UUID:线程id
  3. 设置有效时间,因为我们没有传入所以是默认的30*1000,也就是30s。
  4. 返回空

可见我们的redis锁设计思路是没有问题的,保存了锁名和uuid构成的唯一标识,设置了超时时间。我们接着看,看redisson是怎么实现锁续命的。

红框上一行代码就是我们上锁的代码,我们得到的是个返回值为空的future,来看看红框中的代码。可见核心就一行,就if里面的内容,通过命名我们也可以看出来就是周期重置有效时间。我们点进去看看。

我们先来看看大红框中的lua脚本。先判断锁是否存在,若在将我们的有效时间重置为30s。

然后进行了递归调用,并将调用时间设置为延迟三分之有效时间也就是10s,这样就巧妙的完成了我们的锁续命。

这种递归调用来实现定时任务是很多中间件采用的方法,大神们的写法值得我们学习。

那么,对于无法上锁的线程,redisson又是怎么处理的呢?

在上锁的lua脚本中,对于无法获取锁的线程(红框中)则返回锁的剩余时间,我们在一层层网上看

第一个红框中为redis的channel,类似于mq,是用来通知其他线程锁释放的,后面的大红框为自旋。我们先来看一下后面的部分。

  1. 先是再次尝试加锁,返回锁剩余时间。

看到 semaphore进行tryAcquire,那么我们来找一下到底在哪里release的,大概率是在unlock的方法中实现的。


在unlock解锁方法中,我们看到前后的两个方框中都是发布了一个unlockMessage(为0L),返回了一个1也就是ture。我们来找一下onMessage方法

果然在unlock中进行了release。这时所有的线程就会继续循环,尝试下一次加锁。



版权声明:本文为yfyh2021原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。