主要介绍Redisson可重入锁,通过lua脚本加锁的逻辑源码
目标代码
//通过getLock获取RLock对象,进行加锁
RLock lock = redissonClient.getLock("serviceKey");
lock.lock();
//getLock源码
@Override public RLock getLock(String name) {
return new RedissonLock(connectionManager.getCommandExecutor(), name);
}
源码分析
public RedissonLock(CommandAsyncExecutor commandExecutor, String name) {
super(commandExecutor, name);
//命令执行器,封装了redis的链接,用于操作redis的命令
this.commandExecutor = commandExecutor;
this.id = commandExecutor.getConnectionManager().getId();
//看门狗续期时间(重点)
this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
this.entryName = id + ":" + name;
}
看门狗相关的续期时间配置
public class Config {
......
//看门狗默认续期时间是30s
private long lockWatchdogTimeout = 30 * 1000;
}
加锁相关的代码
public class RedissonLock extends RedissonExpirable implements RLock{
@Override
public void lock() {
try {
//参数1.续期时间
//参数2.时间单位
//参数3.加锁过程是否允许中断
lock(-1, null, false);
} catch (InterruptedException e) {
throw new IllegalStateException();
}
}
@Override
public void lock(long leaseTime, TimeUnit unit) {
try {
lock(leaseTime, unit, false);
} catch (InterruptedException e) {
throw new IllegalStateException();
}
}
}
接下来加锁流程会执行到 RedissonLock.tryLockInnerAsync() 在该方法中,执行加锁的lua脚本代码如下
//如果不存在加锁的key(serviceKey)就执行加锁的逻辑
"if (redis.call('exists', KEYS[1]) == 0) then " +
//hincrby命令:给指定Hash类型Key对应的Map结构的,key对应的value值进行+1
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
//pexpire命令:给指定的Key设置过期时间
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
//判断Hash类型的加锁的key对应的的Map结构中,key对应的value是否存在
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
//将key对应的value值进行+1
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
//pexpire命令:给指定的Key设置过期时间
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
//返回Key(serviceKey)的过期时间
"return redis.call('pttl', KEYS[1]);",
RedissonLock.tryLockInnerAsync() 在该方法中,执行了lus脚本后,内部在调用,evalWriteAsync方法中传入了 internalLockLeaseTime 也就是上文介绍的看门狗锁续期的时间,
这里可以大胆猜测一下,当线程持有锁的时间超时后,如果线程任务仍然没有执行完,会触发看门狗的锁续期操作
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.singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}
RFuture中返回的NodeSource对象,
NodeSource对象是封装了,加锁的Key对应的Redis Master节点/Slave节点 相关的信息
@Override
public <T, R> RFuture<R> evalWriteAsync(String key, Codec codec, RedisCommand<T> evalCommandType, String script, List<Object> keys, Object... params) {
NodeSource source = getNodeSource(key);
return evalAsync(source, false, codec, evalCommandType, script, keys, params);
}
NodeSource对象封装的相关参数如下
private NodeSource getNodeSource(String key) {
//计算当前Key所在槽的值
//如果是cluster模式,则使用crc16算法计算
//如果是主从模式,则使用固定的槽的值
int slot = connectionManager.calcSlot(key);
//通过计算得到的槽的值,获取到当前加锁的Redis节点实例信息
MasterSlaveEntry entry = connectionManager.getEntry(slot);
return new NodeSource(entry, slot);
}
****槽的计算源码
@Override
public int calcSlot(String key) {
if (key == null) {
return 0;
}
int start = key.indexOf('{');
if (start != -1) {
int end = key.indexOf('}');
key = key.substring(start+1, end);
}
//MAX_SLOT =16384
//将Key,CRC16计算出来的值 对16384取模
int result = CRC16.crc16(key.getBytes()) % MAX_SLOT;
log.debug("slot {} for {}", result, key);
return result;
}
Redisson锁数据结构
Redisson存储锁的数据类型是Hash
Hash类型的key包含了当前线程的信息
这里表面数据类型是Hash类型,Hash类型相当于我们java的
<key,<key1,value>> 类型,这里key是指 'redisson'
它的有效期还有9秒,我们再来看里们的key1值为 078e44a3-5f95-4e24-b6aa-80684655a15a:45它的组成是:
guid + 当前线程的ID。后面的value是就和可重入加锁有关。