源码解读
1. Redisson的锁重入源码解读
首先,进入RedissonLock#tryLock(long waitTime, long leaseTime, TimeUnit unit)方法
- waitTime : 获取锁的最大等待时长,第一次获取锁失败不会立即返回,而是在等待时间内不断的尝试,如果这个时间结束了都还没获取成功,才返回false;
- leaseTime : 锁自动失效释放的时间,设置了该值后,Redisson的看门狗续命机制失效;
- unit : 时间单位;
查看尝试获取锁的方法tryAcquire(),发现内部调用的是tryAcquireAsync()
查看tryAcquireAsync()内部方法
- 判断是否设置了锁自动失效释放的时间(过期时间),设置与否走的方法都是
tryLockInnerAsync()
,只不过没有设置就赋默认internalLockLeaseTime
值,这时Redisson会借助看门狗机制给锁自动续命(后面会讲);
查看tryLockInnerAsync()内部方法
- lua脚本部分执行成功返回的是
nil
(类似于我们Java中的null
),执行失败了反而返回一个结果:redis.call(‘pttl’, KEYS[1])
也就是锁的剩余的有效期; - 执行了
pttl
命令,KEYS[1]
是锁的名称,pttl
和ttl
效果是类似的,都是获取key
的剩余有效期,只不过ttl
返回的是秒为单位,pttl
返回的是毫秒为单位;
现在已经拿到了锁的有效期, 我们现在往回倒一步
- 红框内容涉及锁续命,不着急这里先跳过。
- 把
RFuture
返回以后, 这里就有回到了这里get(tryAcquireAsync((wait, leaseTime, threadId))
get()方法就是获取阻塞等待RFuther结果, 等待得到的剩余有效期
这时就回到了这里
- 这里的
subscribe
就是订阅释放锁的lua脚本中的publish
; - 如果等待结束还没有收到通知就取消订阅,并返回获取锁失败;
ttl<time(等待时间)
,代表在等待之间锁就已经释放了;ttl>time(等待时间)
,如果等了time
的时间,经过time
的时间,锁还没有被释放,也就没必要等了;
- 如果时间还很充足,就继续
while(true)
执行上面的代码,不停的尝试等待,不断的进行这样的循环; - 这里设计的巧妙之处就在于利用了消息订阅,信号量的机制,它不是无休止的这种盲等机制,也避免了不断的重试,而是检测到锁被释放才去尝试重新获取,这对CPU十分的友好;
2. WatchDog续约(续命)源码解读
Redisson锁重试的问题是解决了,但是总会发生一些问题,如果我们的业务阻塞超时了ttl
到期了,别的线程看见我们的ttl
到期了,他重试他就会拿到本该属于我们的锁,这时候就有安全问题了,所以该怎么解决?
我们必须确保锁是业务执行完释放的, 而不是因为阻塞而释放的。
- 当我们没有设置
leaseTime
的时候, 也就是leaseTime=-1
的时候过期时间为默认internalLockLeaseTime
; - 查看如下代码可知
internalLockLeaseTime
调用getLockWatchdogTimeout()
赋值默认时间是30s;
- 当
ttlRemainingFuture
的异步尝试获取锁完成以后,先判断执行过程中是否有异常,如果有异常就直接返回了结束执行; - 如果没有发生异常,则判断
ttlRemaining(剩余有效期)
是否为空,为空的话就代表获取锁成功,执行锁到期续约的核心方法scheduleExpectationRenew
;
进入scheduleExpectationRenew方法中查看
EXPIRATION_RENEWAL_MAP中的key我们进去看一下
- 清楚的发现
entryName
由id
和name
两部分组成; id
就是当前的这个连接的id
,name
就是当前锁的名称;
进入更新有效期的方法renewExpectation
- 这个方法主要开启一段定时任务,不断的去更新有效期,定时任务的的时间就是
看门狗时间/3
,也就是10s后刷新有效期;
进入该刷新锁有效期方法
- 这段lua脚本重置有效期,满血复活;
3. 释放锁
进入释放锁方法unlockAsync(long threadId)
进入释放锁的方法unlockInnerAsync(threadId)
进入 取消续命定时任务的方法cancelExpirationRenewal(threadId)
- 先从map中取出任务,先移除任务的线程Id,再取消这个任务,最后再移除entry;
- 到这里看门狗的流程就已经结束了;
4. 结论
watchdog在当前节点存活时每10s给分布式锁的key续期30s;
watchdog机制启动,且代码中没有释放锁操作时,watchdog会不断的给锁续期;
如果程序释放锁操作时因为异常没有被执行,那么锁无法被释放,所以释放锁操作一定要放到finally {}
中;
要使watchLog机制生效,lock时不要设置过期时间;
watchlog的延时时间可以由lockWatchdogTimeout
指定默认延时时间,但是不要设置太小;
watchdog会每lockWatchdogTimeout/3
时间去延时;
watchdog通过类似netty的Future功能来实现异步延时;
watchdog最终还是通过lua脚本来进行延时。