在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
前言主要介绍Redisson可重入锁,通过lua脚本加锁的逻辑源码 目标代码//通过getLock获取RLock对象,进行加锁 RLock lock = redissonClient.getLock("serviceKey"); lock.lock(); //getLock源码 源码分析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锁数据结构参考.https://www.bianchengquan.com/article/570298.html Redisson存储锁的数据类型是Hash Hash类型的key包含了当前线程的信息
这里表面数据类型是Hash类型,Hash类型相当于我们java的 它的有效期还有9秒,我们再来看里们的key1值为 guid + 当前线程的ID。后面的value是就和可重入加锁有关。
问题1、如果在某个机器上的某个线程,已经对key加锁了,那么这台机器上的其他线程如果尝试去对key加锁,会怎么样?肯定是会阻塞住,那么他是如何阻塞住的? 2、如果在某个机器上的某个线程,已经对key加锁了,那么其他机器上的某个线程尝试去对那个key加锁,会怎么样?肯定也是会阻塞住的,那么他是如何阻塞住的呢?
|
请发表评论