深入理解 Redisson 客户端的锁机制:可重入锁、锁重试与看门狗

在分布式系统中,锁机制是保证数据一致性和避免并发冲突的重要手段。Redisson 作为一个强大的 Redis 客户端,提供了丰富且高效的分布式锁实现,其中可重入锁、锁重试和看门狗机制尤为值得关注。

可重入锁

可重入锁允许同一线程多次获取同一把锁,而不会造成死锁。在 Redisson 中,可重入锁的实现利用了 Redis 的 Hash 结构。

获取锁逻辑

  1. 使用exist命令判断当前线程是否存在锁。如果返回值为空,说明锁未被获取,此时创建锁。Hash 结构的key为线程标识,hash-value为当前线程获取锁的次数(初始化为 1)。
  2. 如果exist命令返回值不为空,说明锁已被获取过。此时再次判断当前锁的线程标识与自己线程是否一致:
  3. 如果不一致,说明锁已经被其他线程占用,获取锁失败
  4. 如果一致,说明是重入锁,使用increaby命令将hash-value自增 1,并且刷新key的ttl

释放锁逻辑

在释放锁时,首先判断是否为当前线程的锁。然后取出hash-value,判断其数值大小

  1. 如果value的值大于 1,则将value的值自减
  2. 如果value的值等于 1,则将锁删除
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

public class ReentrantLockExample {
    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        RedissonClient redisson = Redisson.create(config);

        RLock lock = redisson.getLock("myLock");
        try {
            // 获取锁
            lock.lock();
            // 业务逻辑
            System.out.println("线程 " + Thread.currentThread().getName() + " 获取到了锁");
            // 模拟重入
            lock.lock();
            System.out.println("线程 " + Thread.currentThread().getName() + " 再次获取到了锁(重入)");
        } finally {
            // 释放锁
            lock.unlock();
            System.out.println("线程 " + Thread.currentThread().getName() + " 释放了锁");
            lock.unlock(); // 重入情况下,需要多次解锁
        }
        redisson.shutdown();
    }
}

锁重试

锁重试机制允许在获取锁失败后,在指定的重试时间内再次尝试获取锁

获取锁逻辑

  1. 获取锁成功,底层的 Lua 脚本会返回nil(即null),表示获取锁成功,不需要进行重试。
  2. 获取锁失败,底层的 Lua 脚本会返回当前锁的过期时间。拿到过期时间之后,将之前传入的重试时间刷新为剩余时间,判断剩余时间是否大于过期时间:
  • 剩余时间小于过期时间:说明在重试时间范围内已经来不及获取锁,锁已被自动释放。
  • 剩余时间大于过期时间:说明在重试时间范围内还来得及获取锁。此时不会立即重新尝试获取,而是会等待订阅信号量(等待拿到锁的线程释放锁时发出信号)。这个等待订阅也会有默认的等待时间,在收到释放锁信号之后,再次刷新剩余时间,然后才重新尝试获取锁,直到获取锁成功或者超过了剩余时间才会停止。

释放锁逻辑

在释放锁时,会使用publish命令来通知重试获取锁的线程,从而停止他们的等待订阅状态。

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

public class LockRetryExample {
    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        RedissonClient redisson = Redisson.create(config);

        RLock lock = redisson.getLock("myLock");
        try {
            // 尝试获取锁,最多等待10秒,锁的持有时间为30秒
            boolean success = lock.tryLock(10, 30, TimeUnit.SECONDS);
            if (success) {
                System.out.println("线程 " + Thread.currentThread().getName() + " 获取到了锁");
                // 业务逻辑
            } else {
                System.out.println("线程 " + Thread.currentThread().getName() + " 未能获取到锁");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                System.out.println("线程 " + Thread.currentThread().getName() + " 释放了锁");
            }
        }
        redisson.shutdown();
    }
}

看门狗机制

看门狗机制是为了避免业务阻塞时,锁自动超时释放而导致的线程安全问题。

机制原理

tryLock()中,可以传入relaseTime参数,表示锁超时释放的时间。如果不传参,则会执行看门狗机制。一开始,看门狗会默认设置锁 30s 的有效期,之后通过递归来一直刷新 10s 的有效期,防止在业务阻塞时锁自动释放。释放锁时,取消看门狗机制,避免重复刷新有效期

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

public class WatchdogExample {
    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        RedissonClient redisson = Redisson.create(config);

        RLock lock = redisson.getLock("myLock");
        try {
            // 使用看门狗机制获取锁
            lock.lock();
            System.out.println("线程 " + Thread.currentThread().getName() + " 获取到了锁,看门狗机制启动");
            // 模拟业务阻塞
            Thread.sleep(50000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            System.out.println("线程 " + Thread.currentThread().getName() + " 释放了锁,看门狗机制停止");
        }
        redisson.shutdown();
    }
}

你可能感兴趣的:(redis,java,redis,分布式,后端,spring,boot)