Redisson看门狗源码解析

865 阅读3分钟

1.获取锁对象

        //获取一个可重入的锁对象,默认实现为RedissonLock
        RedissonLock testLock = (RedissonLock)redissonClient.getLock("test");

2.对于当前锁对象加锁,然后释放锁

        //阻塞等待直到获取锁
        testLock.lock();
        try {
            System.out.println("获取到redis的锁");
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //判断当前持有锁的线程是否为当前线程
            if(testLock.isHeldByCurrentThread()){
                testLock.unlock();
            }
        }

3.先看lock()方法的实现

    public void lock() {
        try {
            lock(-1, null, false);
        } catch (InterruptedException e) {
            throw new IllegalStateException();
        }
    }

    /**
     * @param leaseTime 申请锁的生存时间(-1为永久)
     * @param unit  时间单位
     * @param interruptibly  是否可中断
     * @throws InterruptedException
     */
    private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
        long threadId = Thread.currentThread().getId();
        //首次尝试获取锁,返回值为当前锁的剩余生存时间
        Long ttl = tryAcquire(-1, leaseTime, unit, threadId);
        //返回值为空说明当前线程成功,否则说明锁已经被占用
        if (ttl == null) {
            return;
        }

        //生成一个订阅锁释放的事件
        RFuture<RedissonLockEntry> future = subscribe(threadId);
        if (interruptibly) {
            commandExecutor.syncSubscriptionInterrupted(future);
        } else {
            //同步执行事件
            commandExecutor.syncSubscription(future);
        }
        //订阅完成后通过循环不断请求锁
        try {
            while (true) {
                ttl = tryAcquire(-1, leaseTime, unit, threadId);
                // 生存时间为空,拿到锁跳出
                if (ttl == null) {
                    break;
                }

                // waiting for message
                if (ttl >= 0) {
                    try {
                        //以阻塞的方式获取锁,最大等待时间为ttl(信号量)
                        future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    } catch (InterruptedException e) {
                        if (interruptibly) {
                            throw e;
                        }
                        future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    }
                } else {
                    if (interruptibly) {
                        future.getNow().getLatch().acquire();
                    } else {
                        future.getNow().getLatch().acquireUninterruptibly();
                    }
                }
            }
        } finally {
            //取消订阅事件
            unsubscribe(future, threadId);
        }
    }

下面看一下tryAcquire()方法

    private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        return get(tryAcquireAsync(waitTime, leaseTime, unit, threadId));
    }

    //阻塞等待RFuture任务返回结果
    public <V> V get(RFuture<V> future) {
        try {
            future.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        if (future.isSuccess()) {
            return future.getNow();
        }
        throw convertException(future);
    }

    private RFuture<Boolean> tryAcquireOnceAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        //生存时间不为-1时,表示请求获取一个有固定生存时间的锁,不需要看门狗的机制
        if (leaseTime != -1) {
            return tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
        }
        //异步调用RedisExecutor执行lua脚本实现加锁
        RFuture<Boolean> ttlRemainingFuture = tryLockInnerAsync(waitTime,
                                                    commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),
                                                    TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
        //注册tryLockInnerAsync完成后的回调事件
        //ttlRemaining是否加锁成功,e为异常信息
        ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
            if (e != null) {
                return;
            }

            // lock acquired
            if (ttlRemaining) {
                //获取锁后将当前线程加入ExpirationEntry,以此来对线程持有的锁进行到期续约
                scheduleExpirationRenewal(threadId);
            }
        });
        return ttlRemainingFuture;
    }

重点看下涉及看门狗机制的scheduleExpirationRenewal()方法

    private void scheduleExpirationRenewal(long threadId) {
        ExpirationEntry entry = new ExpirationEntry();
        //将当前锁对象与ExpirationEntry 的映射关系加入Map
        ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);
        if (oldEntry != null) {
            oldEntry.addThreadId(threadId);
        } else {
            //将当前线程加入类ExpirationEntry 
            //ExpirationEntry 包含两个重要属性Map<Long, Integer> threadIds和Timeout timeout 
            //threadIds保存续约的线程信息,timeout为netty的HashedWheelTimer时间轮的任务实例
            entry.addThreadId(threadId);
            renewExpiration();
        }
    }

    //重点来了
    private void renewExpiration() {
        //释放锁后映射关系会被清除,所以要判断下是否还需要续约
        ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
        if (ee == null) {
            return;
        }
        
        //构建一个时间轮的延迟任务,该任务会在internalLockLeaseTime / 3毫秒后执行
        //关于netty时间轮的实现后面再写一篇来说明
        Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
            @Override
            public void run(Timeout timeout) throws Exception {
                ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
                if (ent == null) {
                    return;
                }
                Long threadId = ent.getFirstThreadId();
                if (threadId == null) {
                    return;
                }
                
                //异步调用renewExpirationAsync执行lua脚本更新锁的生存时间
                RFuture<Boolean> future = renewExpirationAsync(threadId);
                future.onComplete((res, e) -> {
                    if (e != null) {
                        log.error("Can't update lock " + getName() + " expiration", e);
                        return;
                    }
                    if (res) {
                        //重复刷新生存时间
                        renewExpiration();
                    }
                });
            }
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
        
        ee.setTimeout(task);
    }

4.unlock()实现,基本就是将上面一个存储的线程信息和资源释放掉

    public void unlock() {
        try {
            get(unlockAsync(Thread.currentThread().getId()));
        } catch (RedisException e) {
            if (e.getCause() instanceof IllegalMonitorStateException) {
                throw (IllegalMonitorStateException) e.getCause();
            } else {
                throw e;
            }
        }
    }

    public RFuture<Void> unlockAsync(long threadId) {
        RPromise<Void> result = new RedissonPromise<Void>();
        //异步通过lua脚本释放锁
        RFuture<Boolean> future = unlockInnerAsync(threadId);
        future.onComplete((opStatus, e) -> {
            //从EXPIRATION_RENEWAL_MAP移除线程信息
            //把ExpirationEntry中TimeOut从时间轮的延迟任务中注销
            cancelExpirationRenewal(threadId);
            if (e != null) {
                result.tryFailure(e);
                return;
            }
            if (opStatus == null) {
                IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
                        + id + " thread-id: " + threadId);
                result.tryFailure(cause);
                return;
            }
            result.trySuccess(null);
        });
        return result;