Redisson实现的分布式锁核心原理

Redisson 实现的分布式锁核心原理是利用 Redis 的原子操作、数据结构和发布订阅机制,在单节点或集群环境下提供互斥、可重入、自动续期(看门狗)、公平锁等特性。其核心机制如下:


核心原理与流程

  1. 锁获取 (加锁)

    • Lua 脚本保证原子性: 当线程尝试获取锁时,Redisson 会执行一个 Lua 脚本到 Redis 服务器。脚本的核心逻辑是:

      lua 复制代码
      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]);
    • 参数解释:

      • KEYS[1]: 锁的名字(Key),例如 myLock
      • ARGV[1]: 锁的初始生存时间(TTL),单位毫秒(默认 30 秒)。
      • ARGV[2]: 客户端唯一标识(getLockName(threadId) 。通常由 UUID:threadId 组成(如 8743c9c0-0795-4907-87fd-6c719a6b4586:1),用于标识哪个客户端(JVM)的哪个线程持有锁。
    • 脚本逻辑详解:

      1. 锁不存在 (exists == 0): 创建一个 Hash 结构,Key 是锁名,Hash 的 Field 是客户端唯一标识 ARGV[2],Value 设置为 1(表示加锁次数为 1)。设置这个 Key 的过期时间为 ARGV[1](默认 30 秒)。返回 nil 表示加锁成功。
      2. 锁已存在,且持有者是当前线程 (hexists == 1): 将 Hash 中该客户端标识对应的 Value(加锁次数)加 1。重新设置 Key 的过期时间为 ARGV[1]。返回 nil 表示重入加锁成功。
      3. 锁已存在,且持有者不是当前线程: 返回该 Key 剩余的生存时间(pttl),单位毫秒。
  2. 锁获取失败与重试

    • 如果脚本返回的不是 nil(即返回了一个数字,代表剩余 TTL),说明锁被其他线程持有。
    • 客户端(Redisson 内部)会通过 Redis 的发布订阅(Pub/Sub) 机制,订阅一个与该锁名称相关的特定频道(通常是 redisson_lock__channel:{lockName})。
    • 客户端会等待 ,直到收到锁释放的通知消息,或者等待时间超过 ARGV[1] 返回的剩余 TTL(取较小值)。
    • 在等待超时或被唤醒后,客户端会再次尝试执行步骤 1 的 Lua 脚本获取锁(循环重试)。
    • 可配置性: 重试次数、重试间隔、等待时间上限可以通过 lock() 方法的参数或配置进行调整。
  3. 锁续期(看门狗 - Watchdog)

    • 目的: 防止业务逻辑执行时间超过锁的初始 TTL(默认 30 秒)导致锁被 Redis 自动删除,而业务逻辑仍在运行,其他线程可能获得锁引发数据不一致。
    • 机制:
      • 只有在没有指定锁超时时间leaseTime)调用 lock() 时,看门狗才会启动。
      • 当加锁成功(脚本返回 nil)后,Redisson 会在持有锁的客户端启动一个后台定时任务(守护线程)
      • 这个任务默认每 10 秒检查一次客户端是否还持有该锁(通过检查 Hash 结构中对应的 Field 是否存在且 Value > 0)。
      • 如果客户端仍然持有锁,它会重置 该锁 Key 的过期时间回初始值(默认 30 秒)。即 pexpire KEYS[1] 30000
    • 效果: 只要持有锁的客户端(JVM)还在正常运行且没有主动释放锁,看门狗会不断续期,保证业务逻辑有足够时间执行。
  4. 锁释放 (解锁)

    • Lua 脚本保证原子性: 释放锁时同样执行一个 Lua 脚本:

      lua 复制代码
      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
          redis.call('del', KEYS[1]);
          redis.call('publish', KEYS[2], ARGV[1]);
          return 1;
      end;
      return nil;
    • 参数解释:

      • KEYS[1]: 锁名。
      • KEYS[2]: 发布订阅的频道名(redisson_lock__channel:{lockName})。
      • ARGV[1]: 发布的消息内容(通常是解锁消息)。
      • ARGV[2]: 锁的 TTL(用于重入锁释放后重置 TTL)。
      • ARGV[3]: 客户端唯一标识。
    • 脚本逻辑详解:

      1. 检查持有者 (hexists == 0): 如果锁不存在或当前线程不是持有者(Field 不存在),直接返回 nil(可能锁已过期或已被释放,或者不是持有者尝试释放)。这是防止误删他人锁的关键。
      2. 减少重入计数 (hincrby ... -1): 将当前线程对应的重入计数器减 1。
      3. 计数器仍大于 0 (counter > 0): 说明是重入锁,当前线程尚未完全释放锁。重置锁的过期时间为 ARGV[2]。返回 0
      4. 计数器等于 0 (counter == 0): 说明这是最后一次释放(重入计数归零)。
        • 删除整个锁 Key (del KEYS[1])。
        • 发布消息 (publish KEYS[2], ARGV[1]): 向该锁的订阅频道发送一条解锁消息,通知所有正在等待这个锁的客户端(触发步骤 2 中的重试)。返回 1

关键特性支撑

  • 互斥性: Lua 脚本的原子执行确保只有一个客户端能在锁不存在时成功创建锁(Hash 结构)。
  • 可重入性: 使用 Hash 结构的 Field(客户端标识)和 Value(重入次数)记录同一个线程多次加锁。加锁时计数加 1,解锁时计数减 1,计数归零才真正删除锁 Key。
  • 避免死锁/自动释放:
    • 基础: 依赖 Redis Key 的 TTL,即使持有锁的客户端崩溃,锁也会在超时后自动删除。
    • 增强 (看门狗): 后台线程自动续期,防止业务执行时间超过初始 TTL 导致的锁提前失效。
  • 容错性 (单点基本可用): 基于单 Redis 节点即可工作(主从异步复制下存在极端情况下的安全性问题,生产环境建议用 RedLock 或 WAIT 命令增强,但 RedLock 也有争议)。
  • 高效等待: 利用 Redis 的发布订阅机制,避免客户端无意义的轮询(忙等待),减少网络和 Redis 压力。
  • 防止误删: 解锁脚本严格检查客户端标识,确保只有锁的持有者才能释放锁(删除 Key)。

总结

Redisson 分布式锁的核心在于通过 Lua 脚本在 Redis 上原子性地操作一个 Hash 结构来实现锁状态的管理(创建、重入计数、释放) ,同时利用 TTL 防止死锁 ,利用看门狗机制续期 防止业务未完成锁超时失效,并利用发布订阅机制实现高效的锁等待通知。这种设计提供了高性能、高可靠且功能丰富(可重入、公平锁等)的分布式锁实现。

相关推荐
bug菌2 小时前
🤔强一致性 VS 高可用性:你为啥没get到延迟预算的真正意义?
分布式·后端·架构
计算机毕设定制辅导-无忧学长2 小时前
Kafka 集群架构与高可用方案设计(一)
分布式·架构·kafka
你是橙子那我是谁6 小时前
Redis中的分布式锁之SETNX底层实现
数据库·redis·分布式
悟能不能悟6 小时前
Redis的GEO详解
前端·redis·bootstrap
白日依山尽yy9 小时前
docker|redis的面试题——七道
redis·docker·容器
bug菌9 小时前
“强一致”or“最终一致”?别再纠结!你真的懂“异步补偿机制”吗?
分布式·后端·架构
maray13 小时前
分布式顺序数据发生器
数据库·分布式
Edingbrugh.南空19 小时前
Kafka 3.0零拷贝技术全链路源码深度剖析:从发送端到日志存储的极致优化
分布式·kafka
掘金-我是哪吒21 小时前
分布式微服务系统架构第150集:JavaPlus技术文档平台日更
分布式·微服务·云原生·架构·系统架构