【分布式锁篇】Redisson源码分析

文章详细分析了Redisson如何使用Lua脚本来保证分布式锁的原子性和可重入性,同时介绍了看门狗机制,该机制通过定期续约防止锁意外过期,确保线程安全。此外,文中指出Redisson的锁默认有30s的过期时间,并讨论了不同锁获取方式对看门狗机制的影响。
摘要由CSDN通过智能技术生成

Redisson细节分析

一、放开思维,思考redis分布式锁问题

1)redis 实现分布式锁命令: setnx (key,value,expireTime) , key就是锁的资源,value可以用uuid这种,为了防止机器宕机出现死锁,这里可以设置个有效时间。
2)如果代码执行完的话,就需要把锁去掉,让给其他线程争抢。那这里需要考虑3点:
获取锁是否存在,再判断锁是否相等,再去删除,

要思考为什么要引入后续的redisson框架

在这里插入图片描述

由于redis它执行网络命令是单线程执行,在redis执行的时候,可能就无法保证这3条命令的原子性了,所以这里得用lua脚本去保证。

为什么Redis无法保证3条命令原子性呢?(伪代码分析)
答案:其实这3条命令,每一条命令在redis里面都是原子性,但是由于网络问题,可能会出现其他线程争抢,导致这3条命令无法按顺序被Redis一一执行,就不能保证原子性。
我举个例子吧。我们锁执行完后,是不是要删除给其他线程使用,那么我们如果单独写以下伪代码(包括请求Redis三条命令)
1)A线程 hasKey() 判断是否存在
2)A线程 keyValue==UUID+ThreadID
3)A线程如果相等,那么就做del锁,这时候如果你的锁刚好到期了,然后B线程lock住了,然后你这时候del的话,岂不是就把别人的锁删了,就违背分布式锁的意愿了
下面来段伪代码
在这里插入图片描述

用redisson框架的话,它是用lua脚本帮我们去实现的,默认情况下,是30s上锁时间,所以就算机器宕机了,也不会出现死锁,并且它是支持可重入锁的,具体命令就是 hset key1,key2(UUID+ThreadId),,在lua脚本里操作的值。
记住:UUID+ThreadId很重要,因为在JVM中,每台机器的线程ID就是递增的,在分布式场景下单靠一个ThreadId是无法保证唯一性,既然要实现可重入肯定是某一台机器的某一个线程比如A线程拿到锁后,那么只有它才能继续拿到锁可重入。
下面分析下源码证明下使用了UUID:
在这里插入图片描述
这看得出来用的是Lua脚本,具体命令就不分析了,这上面的多条命令可以保证原子性。
在这里插入图片描述
这ID是不是UUID呢?我们去找下
在这里插入图片描述
看得出来是在初始化的时候,就设置了ID,所以每台机器的Lock锁的ID是确定不变的。
在这里插入图片描述
我通过找到这行代码,然后基于找到上层调用的,估计就更明了了
在这里插入图片描述
看到了没,在初始化的时候,会生成一个唯一的UUID,然后根据redisson设计的模式去走具体的类,不管如何这里可以确定的id值为UUID

二、看门狗机制概念流程介绍

1)Redisson看门狗机制就是定期给它的锁Key续约,默认是执行到1/3时间后,比如30s,执行了10s后 还存在当前锁,就会续到最开始的30s,这样又是30s了。
2)续约的线程是基于Netty时间轮做的。
3)然后解锁的话,最后释放锁的时候,会del掉锁,并且publish发布订阅,告诉其他线程锁释放了,你们可以争抢了。
并且最后也会把续约线程关闭,这里肯定不会出现锁没了,再续约问题,因为这里redisssion续约设计做的可以,是当你用了10s,并且续约的时候也会看是否存在当前threadId 和uuid的key,不存在也不会续约。
4)如果你用的是lock(1000,1S),这种就不会有看门狗机制触发。就是时间到了 默认锁就没了,

三、看门狗机制源码分析

在这里插入图片描述
可能有小伙伴问:这明明调用的是同一个方法,为什么会有这种差异呢?
我想说你问的好,但是我们来分析源码,心细的同学可能发现了,上面不带时间的方法是默认传了一个值-1.这个很关键

1).首先我们进入调用的该方法
在这里插入图片描述2).我们再进入里面tryAcquireAsync的方法

在这里插入图片描述

3).我们发现这有个参数leaseTime大小判断,如果我们默认不传的话,看源码可以得知是-1,会进入到else逻辑 发现调用的方法都如下,但是区别就是如果我不传参数就会有个默认值过期时间 internaLockLeasetTime默认是30S
在这里插入图片描述

在这里插入图片描述
4).最重要的一步来了,我们说的看门狗机制,就是在这里区分了
在这里插入图片描述
在这里插入图片描述
5)这个定时续约机制,相当于是基于Netty时间轮做的在这里插入图片描述
这种数据结构比从数据库定时拉取会好很多。时间轮是随着时间一格一格在轮盘在遍历,然后发现任务到期就去执行。

整体分析就讲完了,如果我们传了个大于0的就不会触发看门狗。

总结:

1.Redisson通过Lua脚本实现分布式锁以及保证锁的可重入性(redis的 sexNx命令是不能保证可重入性的,这可能面试官会问你)

2.Redisson中Lua脚本命令是 hset(key1,key2,value1),其中key2是由UUID+“:”+Thread.getId() 组合,为后续保证锁可重入

3.Redisson会有看门狗(Watch Dog)机制,默认不传锁过期时间的话,就会采用默认30S过期,并且用时间轮定期续约

4.Redisson 最后解锁的时候也会判断是否需要关闭续约锁过期时间线程

5.Redisson帮我们去业务上锁更方便,不需要自己考虑很多情况,并且也不会出现死锁问题。

最后:
感觉大家的认真观看,我们来个互动环节吧,Louis给大伙提个问题:
第一次我Lock(key,50s) 传了过期时间那么肯定不会触发看门狗机制,
那么我第二次Lock(key)的时候,不传过期时间会触发看门狗机制,那么这时候的默认看门狗续费时间是什么呢?还是30s吗?
大家也可以一起思考下

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Redisson是一个基于RedisJava驻留库,提供了分布式和线程安全的Java数据结构。Redisson分布式锁实现是基于Redis的setnx命令和Lua脚本实现的。下面是Redisson分布式锁源码分析: 1.获取锁 Redisson分布式锁获取方法是tryAcquire方法,该方法首先会尝试使用setnx命令在Redis中创建一个key,如果创建成功则表示获取锁成功,否则会进入自旋等待。在自旋等待期间,Redisson会使用watchDog机制来监控锁的状态,如果锁被其他线程释放,则会重新尝试获取锁。 2.释放锁 Redisson分布式锁释放方法是release方法,该方法会使用Lua脚本来判断当前线程是否持有锁,如果持有锁则会使用del命令删除锁的key。 3.watchDog机制 Redisson的watchDog机制是用来监控锁的状态的,该机制会在获取锁时启动一个定时任务,定时任务会检查锁的状态,如果锁被其他线程释放,则会重新尝试获取锁。 ```java // 获取锁 public boolean tryAcquire(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException { long time = unit.toMillis(waitTime); long current = System.currentTimeMillis(); final long threadId = Thread.currentThread().getId(); final long leaseTimeInMillis = unit.toMillis(leaseTime); while (true) { if (tryAcquire()) { scheduleExpirationRenewal(threadId, leaseTimeInMillis); return true; } time -= (System.currentTimeMillis() - current); if (time <= 0) { return false; } current = System.currentTimeMillis(); if (Thread.interrupted()) { throw new InterruptedException(); } // watchDog机制 RFuture<RedissonLockEntry> future = subscribe(threadId); if (!future.await(time, TimeUnit.MILLISECONDS)) { return false; } } } // 释放锁 public void unlock() { if (isHeldByCurrentThread()) { unlockInner(); } } private void unlockInner() { Long ttl = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_LONG, "if (redis.call('hexists', KEYS[1], ARGV[2]) == 0) then return nil end; " + "local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1); " + "if (counter > 0) then return 0 end; " + "redis.call('del', KEYS[1]); " + "redis.call('publish', KEYS[2], ARGV[1]); " + "return 1;", Arrays.<Object>asList(getName(), getChannelName()), encode(new UnlockMessage(getName(), getLockName())), id); cancelExpirationRenewal(); if (ttl == null) { throw new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + id + " thread-id: " + Thread.currentThread().getId()); } if (ttl == -1) { get(lockName).deleteAsync(); } } ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值