流程
RedissonClient-(getLock)->RedissonLock-(lock)->tryAcquire-(同步转异步)->tryAcquireAsync-(最终调用)->tryLockInnerAsync-(lua脚本)->lua(脚本原子性,发送)–>redis服务器接受请求,执行脚本
源码
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"return redis.call('pttl', KEYS[1]);",
Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}
解析
● 参数
KEYS[1] 锁名称–Collections.singletonList(getName())
ARGV[1] 过期时间默认30s --internalLockLeaseTime
ARGV[2] 加锁的唯一标识, UUID + threadId—getLockName(threadId)
● lua加锁逻辑
1)先调用redis的exists命令判断加锁的key存不存在,如果不存在的话,那么就进入if。不存在的意思就是还没有某个客户端的某个线程来加锁,第一次加锁肯定没有人来加锁,于是第一次if条件成立。
2)然后调用redis的hincrby的命令,设置加锁的key和加锁的某个客户端的某个线程,加锁次数设置为1,加锁次数很关键,是实现可重入锁特性的一个关键数据。用hash数据结构保存。hincrby命令完成后就形成如下的数据结构。
myLock:{“b983c153-7421-469a-addb-44fb92259a1b:1”:1}
3)最后调用redis的pexpire的命令,将加锁的key过期时间设置为30s。
锁续约
● 默认30秒,是为了防止死锁
● 续约机制
private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
if (leaseTime != -1) {
//加锁
return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
}
RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e != null) {
return;
}
// lock acquired
if (ttlRemaining == null) {
//定时任务,续约
scheduleExpirationRenewal(threadId);
}
});
return ttlRemainingFuture;
}
//续约核心
protected RFuture<Boolean> renewExpirationAsync(long threadId) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return 1; " +
"end; " +
"return 0;",
Collections.<Object>singletonList(getName()),
internalLockLeaseTime, getLockName(threadId));
}
可重入锁
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
● 判断当前已经加锁的key对应的加锁线程跟要来加锁的线程是不是同一个,是就将这个线程对应的加锁次数+1,实现也可重入锁,同事返回nil回去。
//返回值
myLock:{
"b983c153-7421-469a-addb-44fb92259a1b:1":2
}
主动释放锁和避免其他线程释放自己的锁
● 不手动释放的锁因为看门狗机制会造成死锁。
● 指定超时自动释放的会造成锁浪费。
● 释放锁会传入当前线程id避免释放其他锁。