代码片段一、

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加锁,会怎么样?肯定也是会阻塞住的,那么他是如何阻塞住的呢?


图一、

redission 默认开启看门狗吗 redisson看门狗源码_分布式事务框架

图二、

redission 默认开启看门狗吗 redisson看门狗源码_redission 默认开启看门狗吗_02

图三、

redission 默认开启看门狗吗 redisson看门狗源码_redission 默认开启看门狗吗_03