4、Redis分布式锁原理解析

目录

1、Redisson lock 方法原理解析

1. 如果指定了过期时间

2. 如果没有指定过期时间

3. lock 方法的主要步骤

Redisson lock 方法完整代码

分步骤解释

步骤 1:尝试获取锁

步骤 2:获取锁失败,发起订阅

步骤 3:循环等待锁释放和尝试获取锁

小结

2、Redisson tryLock 方法原理解析

1. 如果指定了过期时间

2. 如果没有指定过期时间

3. tryLock 方法的主要步骤

Redisson tryLock 方法完整代码

分步骤解释

步骤 1:尝试获取锁

步骤 2:获取锁失败,计算剩余时间并发起订阅

步骤 3:循环等待锁释放和尝试获取锁

小结

3、Redisson unlock 方法原理解析

unlock 方法的主要步骤

Redisson unlock 方法完整代码

分步骤解释

步骤 1:调用 unlock 方法

步骤 2:异步解锁操作

步骤 3:等待异步操作完成

小结


1、Redisson lock 方法原理解析

1. 如果指定了过期时间
  • 异步续命机制(Watchdog 机制)不再生效,锁会在指定的时间过期并自动释放。
2. 如果没有指定过期时间
  • 启动 Watchdog 机制,自动续命锁,直到显式调用 unlock() 方法释放锁为止。
3. lock 方法的主要步骤

以下是 Redisson lock 方法的完整代码及其详细分步骤解释。

Redisson lock 方法完整代码

private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
    long threadId = Thread.currentThread().getId();  // 获取当前线程ID
    Long ttl = tryAcquire(-1, leaseTime, unit, threadId);  // 尝试获取锁,等待时间为-1,表示无限等待
    if (ttl == null) {  // 如果成功获取到锁,直接返回
        return;
    }

    CompletableFuture<RedissonLockEntry> future = subscribe(threadId);  // 订阅锁释放通知
    pubSub.timeout(future);  // 设置超时回调
    RedissonLockEntry entry;
    if (interruptibly) {
        entry = commandExecutor.getInterrupted(future);  // 获取可中断的锁条目
    } else {
        entry = commandExecutor.get(future);  // 获取锁条目
    }

    try {
        while (true) {
            ttl = tryAcquire(-1, leaseTime, unit, threadId);  // 尝试重新获取锁
            if (ttl == null) {  // 如果成功获取到锁,退出循环
                break;
            }

            if (ttl >= 0) {
                try {
                    entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);  // 等待锁释放通知
                } catch (InterruptedException e) {
                    if (interruptibly) {
                        throw e;
                    }
                    entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);  // 再次等待锁释放通知
                }
            } else {
                if (interruptibly) {
                    entry.getLatch().acquire();  // 等待锁释放通知
                } else {
                    entry.getLatch().acquireUninterruptibly();  // 等待锁释放通知,不可中断
                }
            }
        }
    } finally {
        unsubscribe(entry, threadId);  // 取消订阅
    }
}

分步骤解释

步骤 1:尝试获取锁
  • 方法调用tryAcquire(-1, leaseTime, unit, threadId)
  • 解释
    • 使用 Lua 脚本尝试原子性地获取锁。
    • 如果锁不存在,创建新锁并设置过期时间。
    • 如果锁存在并且由当前线程持有,增加锁的重入计数并重新设置过期时间。
    • 如果成功获取到锁,返回 null,否则返回锁的剩余存活时间。
private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    return evalWrite(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_LONG,
        "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(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));
}
  • Lua 脚本原理

    1. 检查锁是否存在

      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;
      
      • 如果锁不存在(exists 返回 0),则创建一个新的锁,并将其设置为当前线程持有,同时设置过期时间。
    2. 检查锁是否由当前线程持有

      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;
      
      • 如果锁已经存在并且由当前线程持有(hexists 返回 1),则增加锁的重入计数,并重新设置过期时间。
    3. 返回锁的剩余存活时间

      return redis.call('pttl', KEYS[1]);
      
      • 如果锁存在且不由当前线程持有,则返回锁的剩余存活时间。
步骤 2:获取锁失败,发起订阅

如果初次尝试获取锁失败,Redisson 会订阅锁的释放通知。

  • 方法调用subscribe(threadId)
  • 解释
    • 如果初次尝试获取锁失败,Redisson 会订阅锁的释放通知。
    • 通过 subscribe 方法订阅锁的释放通知,以便在锁被释放时能够及时收到通知。
    • pubSub.timeout(future) 设置超时回调,以防订阅过程中出现问题。
    • 使用 commandExecutor.get 或 commandExecutor.getInterrupted 获取订阅结果,根据是否可中断进行选择。
CompletableFuture<RedissonLockEntry> future = subscribe(threadId);
pubSub.timeout(future);
RedissonLockEntry entry;
if (interruptibly) {
    entry = commandExecutor.getInterrupted(future);
} else {
    entry = commandExecutor.get(future);
}
步骤 3:循环等待锁释放和尝试获取锁

在等待锁释放期间,Redisson 会进入一个循环,不断尝试重新获取锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值