带你看Redisson分布式加锁解锁源码,深入理解加锁原理

136 阅读5分钟

Redisson分布式锁基础使用

//获取锁对象
RLock lock = redissonClient.getLock("lockTest");
//加锁
lock.lock();
try{
    System.out.println("业务代码!");
}finally {
    //释放锁
    lock.unlock();
}

RedissonLock构造方法

public RedissonBaseLock(CommandAsyncExecutor commandExecutor, String name) {  
    // 调用父类构造函数,传递命令执行器和锁的名称
    super(commandExecutor, name);
    // 将传入的命令执行器赋值给当前对象的commandExecutor字段
    this.commandExecutor = commandExecutor;
    // 从连接管理器获取当前实例的唯一标识符并赋值给id字段,UUID.randomUUID()生成
    this.id = commandExecutor.getConnectionManager().getId();
    // 从连接管理器的配置中获取锁看门狗超时时间(默认30s),并赋值给internalLockLeaseTime字段
    this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
    this.entryName = this.id + ":" + name;
}

lock()加锁

/*
    lock对外提供两个重载方法,一个默认使用redisson的续期,
    一个使用自己设置的超时时间,不启用自动续期功能
*/

public void lock(long leaseTime, TimeUnit unit) {
    try {
        this.lock(leaseTime, unit, false);
    } catch (InterruptedException var5) {
        throw new IllegalStateException();
    }
}


public void lock() {
    try {
        this.lock(-1L, (TimeUnit)null, false);
    } catch (InterruptedException var2) {
        throw new IllegalStateException();
    }
}

获取锁逻辑

/*
  -   `leaseTime`:锁的持有时间,如果在此时间内没有释放锁,则锁将自动释放。
  -   `unit`:`leaseTime`的时间单位。
  -   `interruptibly`:指示线程在等待锁时是否可以被中断。
*/
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
    //获取当前线程的线程id
    long threadId = Thread.currentThread().getId();
    // 尝试获取锁并返回剩余的超时时间
    Long ttl = this.tryAcquire(-1L, leaseTime, unit, threadId);
    if (ttl != null) {
        //调用`subscribe`方法订阅锁的状态变化,这样当前线程可以感知到锁被其他线程释放的事件。
        RFuture<RedissonLockEntry> future = this.subscribe(threadId);
        //根据`interruptibly`参数,使用不同的同步方法来处理订阅的完成,以支持中断。
        if (interruptibly) {
            this.commandExecutor.syncSubscriptionInterrupted(future);
        } else {
            this.commandExecutor.syncSubscription(future);
        }

        try {
            //循环等待
            while(true) {
                //再次尝试获取锁
                ttl = this.tryAcquire(-1L, leaseTime, unit, threadId);
                //`ttl`为`null`,表示锁已被其他线程释放且当前线程成功获取,此时退出循环
                if (ttl == null) {
                    return;
                }

                if (ttl >= 0L) {
                    try {
                    // 阻塞ttl时间,让出cpu,避免占用资源
                    // 或者接受到订阅消息唤醒,会继续获取锁,并且根据interruptibly判断是否需要抛出打断异常
                        ((RedissonLockEntry)future.getNow()).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    } catch (InterruptedException var13) {
                        if (interruptibly) {
                            throw var13;
                        }

                        ((RedissonLockEntry)future.getNow()).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    }
                    //如果`ttl`小于0(实际在代码中不可能出现,因为`tryAcquire`在成功获取锁时不会返回负数),则根据`interruptibly`参数决定是否以可中断或不可中断的方式等待`Latch`。
                } else if (interruptibly) {
                    ((RedissonLockEntry)future.getNow()).getLatch().acquire();
                } else {
                    ((RedissonLockEntry)future.getNow()).getLatch().acquireUninterruptibly();
                }
            }
        } finally {
            this.unsubscribe(future, threadId);
        }
    }
}

/*
-   `long waitTime`:等待锁的最长时间。
-   `long leaseTime`:锁的持有时间,如果为`-1`,则使用默认的锁持有时间。
-   `TimeUnit unit`:`waitTime`和`leaseTime`的时间单位。
-   `long threadId`:尝试获取锁的线程的ID,用于在Redis中唯一标识锁。
*/
private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    // 调用 tryAcquireAsync 方法异步尝试获取锁,并等待结果
    return (Long)this.get(this.tryAcquireAsync(waitTime, leaseTime, unit, threadId));
}

开启锁续命逻辑

根据leaseTime是否等于-1来判断是否开启锁续命逻辑

private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    RFuture ttlRemainingFuture;
    if (leaseTime != -1L) {
        //如果不是-1,则以开发者指定的过期时间为准
        //异步获取锁
        ttlRemainingFuture = this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    } else {
        //如果是-1,则以默认的过期时间30s为准
        //异步获取锁
        ttlRemainingFuture = this.tryLockInnerAsync(waitTime, this.internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
    }
    // 通过`onComplete`方法为`ttlRemainingFuture`添加一个监听器。当异步操作完成时,这个监听器会被调用。
    ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
    // 如果操作成功(`e == null`),并且返回的TTL剩余时间为`null`(表示成功获取了锁),则根据`leaseTime`的值更新`internalLockLeaseTime`或调用`scheduleExpirationRenewal`方法来安排锁的续期。
        if (e == null) {
            if (ttlRemaining == null) {
                if (leaseTime != -1L) {
                    this.internalLockLeaseTime = unit.toMillis(leaseTime);
                } else {
                    this.scheduleExpirationRenewal(threadId);
                }
            }

        }
    });
    return ttlRemainingFuture;
}

异步获取锁的lua脚本调用

Redisson底层是使用Redis的Hash数据结构进行加锁的,key为开发者自定义的名称,filed为线程id,value为重入次数。

<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
    // 使用Redis的EVAL命令执行一段Lua脚本,尝试获取锁
    return commandExecutor.evalWriteAsync(this.getRawName(), LongCodec.INSTANCE, command,
        "if (redis.call('exists', KEYS[1]) == 0) then " +
            //如果锁不存在
            // 使用HSET命令将锁信息存储到Redis哈希表中
            "redis.call('hset', KEYS[1], ARGV[2], 1); " +
            // 设置锁的过期时间(默认30毫秒)
            "redis.call('pexpire', KEYS[1], ARGV[1]); " +
            // 返回nil表示成功获取锁,对应java的null
            "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]); " +
            // 返回nil表示成功获取锁
            "return nil; " +
        "end; " +
        // 如果锁已被其他线程持有,返回锁的剩余过期时间
        "return redis.call('pttl', KEYS[1]);",
        // getRawName()对应KEYS[1]、unit.toMillis(leaseTime)对应ARGV[1]、this.getLockName(threadId)对应ARGV[2]
         Collections.singletonList(this.getRawName()), new Object[]{unit.toMillis(leaseTime), this.getLockName(threadId)})
}

锁续期(看门狗机制)

protected void scheduleExpirationRenewal(long threadId) {
    ExpirationEntry entry = new ExpirationEntry();
    ExpirationEntry oldEntry = (ExpirationEntry)EXPIRATION_RENEWAL_MAP.putIfAbsent(this.getEntryName(), entry);
    //判断是否已经存在续约任务
    if (oldEntry != null) {
        oldEntry.addThreadId(threadId);
    } else {
        entry.addThreadId(threadId);
        this.renewExpiration();
    }

}

private void renewExpiration() {
    ExpirationEntry ee = (ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
    //判断该锁是否存在续约任务
    if (ee != null) {    
        // 创建一个新的定时任务
        Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
            public void run(Timeout timeout) throws Exception {
                //再次判断是否存在续约任务
                ExpirationEntry ent = (ExpirationEntry)RedissonBaseLock.EXPIRATION_RENEWAL_MAP.get(RedissonBaseLock.this.getEntryName());
                if (ent != null) {
                    Long threadId = ent.getFirstThreadId();
                    if (threadId != null) {
                        //通过lua脚本给锁续期
                        RFuture<Boolean> future = RedissonBaseLock.this.renewExpirationAsync(threadId);
                        //如果续期失败则删除续约任务
                        future.onComplete((res, e) -> {
                            if (e != null) {
                                RedissonBaseLock.log.error("Can't update lock " + RedissonBaseLock.this.getRawName() + " expiration", e);
                                RedissonBaseLock.EXPIRATION_RENEWAL_MAP.remove(RedissonBaseLock.this.getEntryName());
                            } else {
                                if (res) {
                                    RedissonBaseLock.this.renewExpiration();
                                }

                            }
                        });
                    }
                }
            }
            //每过 30s / 3 == 10s 续约一次
        }, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
        //设置定时任务
        ee.setTimeout(task);
    }
}

protected RFuture<Boolean> renewExpirationAsync(long threadId) {
    return this.evalWriteAsync(this.getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, 
    "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + 
        "redis.call('pexpire', KEYS[1], ARGV[1]);" +
        "return 1; " +
     "end;"  +
     "return 0;", 
     Collections.singletonList(this.getRawName()), this.internalLockLeaseTime, this.getLockName(threadId));
}

解锁

public void unlock() {
    try {
        //异步解锁,并等待获取结果
        this.get(this.unlockAsync(Thread.currentThread().getId()));
    } catch (RedisException var2) {
        if (var2.getCause() instanceof IllegalMonitorStateException) {
            throw (IllegalMonitorStateException)var2.getCause();
        } else {
            throw var2;
        }
    }
}


public RFuture<Void> unlockAsync(long threadId) {
    RPromise<Void> result = new RedissonPromise();
    //异步解锁
    RFuture<Boolean> future = this.unlockInnerAsync(threadId);
    future.onComplete((opStatus, e) -> {
        //不管是否解锁成功,都会取消锁的续期
        this.cancelExpirationRenewal(threadId);
        if (e != null) {
            result.tryFailure(e);
        } else if (opStatus == null) {
            IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + this.id + " thread-id: " + threadId);
            result.tryFailure(cause);
        } else {
            result.trySuccess((Object)null);
        }
    });
    return result;
}

//如果锁被成功解锁(即计数器减到0),Lua脚本返回`1`;如果锁存在但计数器未减到0,返回`0`;如果锁不存在,返回`nil`
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
    return this.evalWriteAsync(this.getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, 
    //如果当前锁重入次数减为0,则返回nil
    "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then "+
    "   return nil;"+
    "end; "+
    //否则重入次数减一
    "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); "+
    "if (counter > 0) then "+
        //如果重入次数大于零,则续期
    "    redis.call('pexpire', KEYS[1], ARGV[2]); " +
    "    return 0; " +
    "else "+ 
        //否则删除对应的key
    "    redis.call('del', KEYS[1])" +; 
        //通过publish命令在指定的频道上发布一个解锁消息
    "    redis.call('publish', KEYS[2], ARGV[1])" +; 
    "    return 1" +; 
    "end "+; 
    "return nil;",
    Arrays.asList(this.getRawName(), this.getChannelName()), new Object[]{LockPubSub.UNLOCK_MESSAGE, this.internalLockLeaseTime, this.getLockName(threadId)});
}

代码小白,各位大佬如果发现有什么解释不对的地方,欢迎讨论!!!