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 防止死锁 ,利用看门狗机制续期 防止业务未完成锁超时失效,并利用发布订阅机制实现高效的锁等待通知。这种设计提供了高性能、高可靠且功能丰富(可重入、公平锁等)的分布式锁实现。

相关推荐
小花鱼20252 小时前
redis在Spring中应用相关
redis·spring
郭京京2 小时前
redis基本操作
redis·go
似水流年流不尽思念2 小时前
Redis 分布式锁和 Zookeeper 进行比对的优缺点?
redis·后端
郭京京2 小时前
go操作redis
redis·后端·go
Warren984 小时前
Spring Boot 拦截器返回中文乱码的解决方案(附全局优化思路)
java·网络·spring boot·redis·后端·junit·lua
XXD啊4 小时前
Redis 从入门到实践:Python操作指南与核心概念解析
数据库·redis·python
小陈永不服输13 小时前
Windows下RabbitMQ完整安装指南
windows·分布式·rabbitmq
NPE~17 小时前
[docker/大数据]Spark快速入门
大数据·分布式·docker·spark·教程
Java小混子19 小时前
【Redis】缓存和分布式锁
redis·分布式·缓存
柯南二号20 小时前
【Java后端】【可直接落地的 Redis 分布式锁实现】
java·redis·分布式