• Redisson 实现分布式锁源码浅析


    代码示例

    @Slf4j
    public class WatchDogDemo {
    
        static Config config = null;
        static Redisson redisson = null;
        static final String KEY_WATCH = "watch_dog_key";
        static{
            config = new Config();
            config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0);
            redisson = (Redisson)Redisson.create();
        }
    
        public static void main(String[] args) {
            RLock lock = redisson.getLock(KEY_WATCH);
            lock.lock();
            try {
                log.info(">>>>>>>>11111.......abc");
                TimeUnit.SECONDS.sleep(25);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    流程解析

    直接从 lock() 和 unlock() 看起,首先跟踪进入到 lock() 方法核心,如下:

    在这里插入图片描述

    继续进入到核心方法,如下:

    在这里插入图片描述

    如果自己不设置过期时间,那么底层默认会给你设置 30s 作为锁过期时间,如下:

    在这里插入图片描述

    在这里插入图片描述

    进入到 tryLockInnerAsync() 加锁的方法如下:

    在这里插入图片描述

    会发现就是通过三段 lua 脚本指令去加锁,其中 KEYS[1] 代表是你加锁的那个 key,ARGV[1] 代表过期时间(默认30s)、ARGV[2] 代表加锁的客户端 ID。

    第一段 lua 脚本表示: 第一个客户端过来判断是否存锁 key 是否已经存在,第一次过来很明显不存在这个 key,所以需要去上锁,从而执行 hincrby 指令开始加锁,并且给这个客户端分配了一个唯一标识的客户端 ID (ARGV[2]) ,并且通过expire设置过期时间为 30s,然后最后返回 null。

    第二段 lua 脚本表示:假如第二次过来的还是第一个客户端,判断已经存在锁了,那么就要去执行锁重入操作,给这个客户端的锁次数 +1,在释放锁的时候也要做相应的 -1 操作,然后最后返回 null。

    第三段 lua 脚本表示:假设第二次过来的是另一个客户端,会先执行第一段判断第二个客户端是否要加锁,假设第一个客户端还没有释放掉锁,那么第二个客户端就不能加锁成功,只能去执行第三段 lua 指令,然后最后返回第一个客户端持有锁的过期时间,注意前两步都是返回的 null,只有最后一步是返回持有锁的过期时间。

    可以总结出一句话,分布式锁的加锁和设置过期时间都是通过三段 lua 脚本指令实现的,一条指令的执行具备原子性

    接着继续分析下面一段代码,如下:

    在这里插入图片描述

    通过 tryLockInnerAsync() 方法已经可以尝试加锁:

    第一种情况:

    如果加锁成功,然后 lua 脚本直接就会返回 null,所以 ttlRemaining 变量值就是 null,然后进入 if 逻辑。leaseTime 默认传入的值是 -1。所以走 else 分支的逻辑。

    重点看这个 scheduleExpirationRenewal() 方法,如下:

    在这里插入图片描述

    首先能够看到这是一个递归调用,自己调用自己,递归调用最容易出现的就是栈溢出问题,所以得给一个休息的时间,所以每隔 1/3 时间才会去执行一次,默认时间 30s,所以就是每隔 10s 执行一次,主要去干这样一件事情,重新刷新锁的过期时间

    把这段代码简化成自己能看到的代码如下所示:

    在这里插入图片描述

    然后主要是在刷新锁的过期时间(虽然已加锁成功就开启了 watchdog 监控🐶,但是并不是一开始就去刷新时间,而是过了 10s 才会去刷新这个过期时间)。进入 renewExpirationAsync() 方法如下:

    在这里插入图片描述

    会发现也是两段 lua 脚本指令:

    第一段会去判断你这个锁是否还被人持有着,只要是这把锁还在被人使用,那么肯定不能让这把锁过期失效,所以要给这个把锁续命,或者说重新刷新回默认的 30s,ARGV[1] 就是表示一开始赋值的 30s 时间。最后一定要非常注意返回的是 1。

    第二段自然就是表示锁没有人使用了呗,那么肯定就不用去做续命了,直接返回 0

    然后继续往下看代码,如下:

    在这里插入图片描述

    如果 lua 返回 1,res 就是 true,如果 lua 返回 0,res 就是 false,返回 1 表示需要续命,那就会在此调用自己 renewExpiration() 方法,然后又去执行这段 lua 脚本重新刷新锁过期时间。

    什么时候能够跳出呢?当锁不需要续命(过期或者释放了)的时候,那么这里就会返回 0,走 else 逻辑就可以跳出了,自己不去调用自己了不就可以退出了么。

    第二种情况:

    如果加锁不成功,然后 lua 脚本直接就会返回这个锁还有多少时间过期,所以 ttlRemaining 变量值不为 null。直接走 return CompletableFutureWrapper(f) 逻辑

    在这里插入图片描述

    然后继续观察代码如下:

    在这里插入图片描述

    tryAcquire(-1, leaseTime, unit, threadId) 表示继续尝试去加锁是否能成功,假设还是失败的,继续看下图:

    在这里插入图片描述

    ttl 返回值大于0 表示加锁失败,那么就要让这个线程不要这么频繁的去尝试了,等人家通知你吧,这里就使用到了 AQS 里面闭锁 Semaphore 信号量作为拦截,信号大小初始设置成 0 了,表示要让这个线程去睡眠了

    在这里插入图片描述

    最终会调用 park() 方法线程被挂起,等着被唤醒(等着锁释放或者其他线程被中断了就会醒来)

    在这里插入图片描述

    最后再来看下锁释放的源码如下:

    在这里插入图片描述

    主要干两件事情,一是去释放锁肯定是去执行 lua 脚本指令,毕竟加锁都是使用的 lua,二是去取消 watchdog 的锁续命操作。

    最后看下这个解锁的 lua 脚本如下:

    在这里插入图片描述

    也是分成对应的三段:

    第一段是:如果发现当前线程和加锁线程不是同一个线程,不允许解锁,直接返回 null,解锁失败。

    第二段是:每次调用 hincrby 递减 1,释放一次锁,如果发现剩余次数还大于 0 ,表示是自增锁,那就在把过期时间重新刷新下。

    第三段是:调用 del 删除锁,并且还发布了删除消息,返回 1 表示释放锁成功。

  • 相关阅读:
    聊聊更新表时的隐式提交
    《FAQ专场 | smardaten及应用软件的运维管理(上)》
    新版AI系统ChatGPT源码支持GPT-4/支持AI绘画去授权
    与CSDN相识的第五周年
    『牛客|每日一题』N皇后问题
    Win10怎么打开管理员命令提示符窗口
    【译】2023年——社区实验的一年
    python 微信发送消息
    微信小程序 java网上购物商城系统
    『现学现忘』Git后悔药 — 28、版本回退git reset --soft命令说明
  • 原文地址:https://blog.csdn.net/qq_35971258/article/details/126866124