代码片段一、
public static void main(String[] args) throws Exception { Config config = new Config(); config.useClusterServers() .addNodeAddress("redis://192.168.0.107:7001") .addNodeAddress("redis://192.168.0.107:7002") .addNodeAddress("redis://192.168.0.110:7003") .addNodeAddress("redis://192.168.0.110:7004") .addNodeAddress("redis://192.168.0.111:7005") .addNodeAddress("redis://192.168.0.111:7006"); RedissonClient redisson = Redisson.create(config); // 进入代码片段二中 RLock lock = redisson.getLock("anyLock"); //获取到了锁之后,就可以lock了,代码片段五、 lock.lock(); Thread.sleep(1000); lock.unlock(); RMap map = redisson.getMap("anyMap"); map.put("foo", "bar"); map = redisson.getMap("anyMap"); System.out.println(map.get("foo")); }
代码片段二、
@Overridepublic RLock getLock(String name) { // 代码片段三、这里的name其实就是我们业务代码里面的anyLock这个名字 // 代码片段四、 return new RedissonLock(connectionManager.getCommandExecutor(), name);}
代码片段三、
// 其实这里就是获取一个commandExecutor,这个命令执行器其实就可以理解成和底层Redis进行交互的一个执行器public CommandSyncService getCommandExecutor() { return commandExecutor;}
代码片段四、debug的结果如图一、
// 这里创建了一个RedissonLock的对象public RedissonLock(CommandAsyncExecutor commandExecutor, String name) { super(commandExecutor, name); // 命令执行器 this.commandExecutor = commandExecutor; // 这里的id其实就是什么呢,就是一个uuid this.id = commandExecutor.getConnectionManager().getId(); // 这里的leaseTime其实就是默认的30秒,我理解应该就是,如果30秒还没有获取锁的话,就会有相应的操作,具体什么操作,需要看看后面的源码,这个参数还是很重要的,跟watchdog看门狗有关系的 // watchdog,看门狗,看起来是一个后台定时不断的执行的后台线程,我们直接去redisson源码包里找一下,尝试打一些断点看看看门狗的 this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(); //entryName对ID和name进行一个拼接 this.entryName = id + ":" + name;}
代码片段五、
@Overridepublic void lock() { try { // 代码片段六、 lockInterruptibly(); } catch (InterruptedException e) { // 这里面其实写的还是蛮好的,为什么呢,反正我个人写代码大部分的bug都来自于最后资源的释放没有考虑到,也就是很少考虑异常的情况 // 这里面如果lock失败的话,就中断这个线程的执行 Thread.currentThread().interrupt(); }}
代码片段六、
@Overridepublic void lock() { try { // 进入代码片段七、 lockInterruptibly(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }}
代码片段八、
@Overridepublic void lockInterruptibly() throws InterruptedException { // 这里面的-1其实就是lock的时间,没有说指定多久之内必须lock成功,我理解应该是阻塞式的lock // 代码片段八、这里的-1其实我的理解是没有超时限制 lockInterruptibly(-1, null);}
代码片段八、
@Override public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException { // 获取到当前线程的ID,其实源码看到这里,目前来看,这个redisson的源码写的还是蛮不错的,最起码代码看起来比较清晰 long threadId = Thread.currentThread().getId(); // 这里就开始tryAcquire,让人想到了AQS源码的编码风格,很熟悉的味道,liaseTime就是-1 // 代码片段九、 Long ttl = tryAcquire(leaseTime, unit, threadId); // lock acquired if (ttl == null) { return; } RFuture future = subscribe(threadId); commandExecutor.syncSubscription(future); try { while (true) { ttl = tryAcquire(leaseTime, unit, threadId); // lock acquired if (ttl == null) { break; } // waiting for message if (ttl >= 0) { getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } else { getEntry(threadId).getLatch().acquire(); } } } finally { unsubscribe(future, threadId); }// get(lockAsync(leaseTime, unit)); }
代码片段九、
private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) { // 代码片段十、 return get(tryAcquireAsync(leaseTime, unit, threadId));}
代码片段十、
private RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) { // 默认情况下,我们是没有设置leaseTime的,也就是说,这个leaseTime的值是-1,该逻辑是不会走到的 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.addListener(new FutureListener<Long>() { @Override public void operationComplete(Future<Long> future) throws Exception { if (!future.isSuccess()) { return; } Long ttlRemaining = future.getNow(); // lock acquired if (ttlRemaining == null) { scheduleExpirationRenewal(threadId); } } }); return ttlRemainingFuture;}
代码片段十一、
RFuture tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand command) { internalLockLeaseTime = unit.toMillis(leaseTime); // 下面这段lua脚本才是lock的核心逻辑了 代码片段十二、 return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command, //,KEYS[1]一看就是我们设置的那个锁的名字anyLock,先执行了redis的exists的指令,判断一下, "if (redis.call('exists', KEYS[1]) == 0) then “ + //如果“anyLock”这个key不存在,那么就进行加锁,实际加锁的指令 //hset,redis的一个指令,相当于是在redis的一个map数据结构里设置一个key value //KEYS[1]其实可以理解为就是我们设置的那个key,“anyLock”,ARGV[1]其实就是一个这个key的过期时间, //可能是默认的一个值,叫做30000毫秒,30s,很有可能是说的是,这个anyLock这个key对应的过期时间就是30秒 "redis.call('hset', KEYS[1], ARGV[2], 1); “ + //pexpire KEYS[1] ARGV[1],设置一个key的过期时间 "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return nil; " + "end; “ + //hexits KEYS[1] ARGV[2],这个意思就是说针对KEYS[1](anyLock)这个名字的一个map,里面是否存在一个ARGV[2]的一个key(lockState), // 如果是存在的话,hincrby KEYS[1] ARGV[2] 1,将anyLock这个map中的lockState这个key的值累加1 "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + "redis.call('hincrby', KEYS[1], ARGV[2], 1); “ + //又执行了一下pexpire anyLock 30000,再一次将anyLock这个key的有效期设置问了30秒 "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return nil; " + "end; “ + //就是返回anyLock这个key当前还剩下的一个有效的存活期,就是当前这个key还能存活多少毫秒,或者多少秒这样子,30000毫秒,29998毫秒,2毫秒已经过去了 "return redis.call('pttl', KEYS[1]);", Collections.singletonList(getName()), internalLockLeaseTime, getLockName(threadId));}
代码片段十二、
@Overridepublic RFuture evalWriteAsync(String key, Codec codec, RedisCommand evalCommandType, String script, List<Object> keys, Object... params) { // 根据anyLock这个key,尝试去获取到一个NodeSource的这么一个东西,我们可以认为说,我们现在不是有3主3从的一个redis集群,用屁股推测和猜想一下,是不是有可能说他是通过key获取一个NodeSource // 代码片段十三、 NodeSource source = getNodeSource(key); // 代码片段十五、 return evalAsync(source, false, codec, evalCommandType, script, keys, params);}
代码片段十三、
private NodeSource getNodeSource(String key) { // redis cluster默认的slot数量,是16384,无论你创建的是多大的一个redis cluster,他统一有一个slot的一个划分,就是将集群的存储空间划分为16384个slot,这16384个slot就平均分布在各个master实例上 // redis cluster的slot可以理解成一个索引一样的一个东西,当然,可能不是那么的准确 // 代码片段十四、 int slot = connectionManager.calcSlot(key); // entry的结果如图二、 MasterSlaveEntry entry = connectionManager.getEntry(slot); return new NodeSource(entry);}
代码片段十四、
@Overridepublic 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); } // 其实就是对16384的一个取模 int result = CRC16.crc16(key.getBytes()) % MAX_SLOT; log.debug("slot {} for {}", result, key); return result;}
代码片段十五、
private RFutureevalAsync(NodeSource nodeSource, boolean readOnlyMode, Codec codec, RedisCommand evalCommandType, String script, List keys, Object... params) { RPromise mainPromise = createPromise(); List args = new ArrayList(2 + keys.size() + params.length); args.add(script); args.add(keys.size()); // keys,是一个数组,[anyLock],这个其实对应的就是上面分析的那个脚本里的KEYS[1] args.addAll(keys); // params,是一个参数,[30000,8743c9c0-0795-4907-87fd-6c719a6b4586:1],代表的其实就是ARGV[1]和ARGV[2], // 如图三、 // 这样的话,其实之前的那段脚本里面的值就出来了 //KEYS[1] = anyLock //ARGV[1] = 30000 //ARGV[2] = 8743c9c0-0795-4907-87fd-6c719a6b4586:1 //8743c9c0-0795-4907-87fd-6c719a6b4586,代表的是一个UUID, // 其实就是这个客户端上的一个ConnectionManager的这么一个id,1是什么呢?1大家还记得吧,其实就是threadId,大体上可以认为是一个客户端的一个线程对应的唯一的标识 args.addAll(Arrays.asList(params)); async(readOnlyMode, nodeSource, codec, evalCommandType, args.toArray(), mainPromise, 0, false, null); return mainPromise;}
总结:
- pexpire anyLock 30000:anyLock这个key只能存活30000毫秒,30秒,默认情况下,你使用redisson加锁,其实不是无限制的不停的可以拥有这把锁的,人家默认情况下给你设置的一个锁的有效期就是30秒,watchdog看门狗的代码的实现,人家一定是在不断的监控这个锁,如果到30秒,就自动释放掉这把锁,或者是自动延期这个key的有效时长
- redisson实现了一个看门狗,推测,根据文档里面的东西来推测,可能这个看门狗到了30秒,就自动把这个key给延期了,在延期的时候,他可能是怎么做的呢?如果看门狗在30秒内再次执行了这个加锁的方法,此时很可能会走到下面的那段脚本,其实脚本中的第二个if里面的逻辑
- 看门狗,watchdog到底是干嘛的,接下来的笔记会对看门狗代码进行剖析
问题:
问题一、如果在某个机器上的某个线程,已经对key加锁了,那么这台机器上的其他线程如果尝试去对key加锁,会怎么样?肯定是会阻塞住,那么他是如何阻塞住的?
问题二、如果在某个机器上的某个线程,已经对key加锁了,那么其他机器上的某个线程尝试去对那个key加锁,会怎么样?肯定也是会阻塞住的,那么他是如何阻塞住的呢?
图一、
图二、
图三、