Redisson 可重入分布式锁底层原理详解 一、什么是可重入锁可重入同一个线程在已经持有锁的前提下可以再次获取同一把锁不会出现自己把自己阻塞死的情况。本地锁中ReentrantLock、synchronized都是可重入锁。 而我们手写的简易 Redis 锁默认不支持可重入 线程拿到锁之后再次执行加锁代码会直接获取锁失败造成死锁。 所以生产环境分布式锁一般要求支持可重入Redisson 的 RLock 就是可重入分布式锁。应用场景A 方法获取锁A 内部调用 B 方法B 方法也需要获取同一把锁此时就需要锁支持重入。二、Redisson 锁在 Redis 中的存储结构Redisson 底层不是简单的key-value字符串而是使用hash 哈希结构存储锁信息key: lock:goods:1001 hash field: 线程唯一标识uuid threadId hash value: 重入计数count示例lock:goods:1001 35678190-1234-4567-8888:thread-1 - 1key锁名称field标记哪个线程持有这把锁value重入次数每重入一次 count1释放一次 count-1整个 hash 还会设置过期时间加锁逻辑Lua 脚本Redisson 加锁、重入、释放全部依靠 Lua 脚本保证原子性。-- 判断锁key是否不存在 if (redis.call(exists, KEYS[1]) 0) then -- 不存在新增hash计数1设置过期时间 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 -- 是当前线程计数1返回nil加锁成功 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]锁 keyARGV[1]锁过期时间毫秒ARGV[2]当前线程唯一标识uuid:threadId流程解读如果锁不存在创建 hash计数置 1设置过期时间加锁成功如果锁存在并且是当前线程的锁hincrby计数 1刷新过期时间重入成功如果锁存在但属于别的线程返回锁剩余有效期加锁失败三、释放锁 Lua 脚本释放锁不能直接 del要递减计数计数减到 0 才删除 key。-- 判断锁是否属于当前线程 if (redis.call(hexists, KEYS[1], ARGV[2]) 0) then return nil; end; -- 计数-1 local counter redis.call(hincrby, KEYS[1], ARGV[2], -1); if (counter 0) then -- 计数0说明还有重入只刷新过期时间不删除锁 redis.call(pexpire, KEYS[1], ARGV[1]); return 0; else -- 计数等于0删除锁key释放完成 redis.call(del, KEYS[1]); return 1; end;逻辑不是当前线程的锁直接返回禁止释放别人的锁计数 - 1计数 0还有重入锁不删除更新过期时间计数 0删除锁 key锁完全释放四、看门狗 WatchDog 自动续期机制重点面试题什么时候开启看门狗当你调用lock.lock()不指定过期时间Redisson 会自动启动看门狗如果手动指定 leaseTime看门狗不会自动开启需要自己处理锁超时风险。默认规则锁默认过期时间30s看门狗定时任务每 10s 执行一次过期时间的 1/3定时任务逻辑检查锁是否还被当前线程持有持有则刷新锁过期时间回到 30s原理获取锁成功后开启一个异步定时线程不断延长锁有效期。只要业务没执行完毕锁就不会过期业务执行结束锁释放看门狗任务停止。⚠️注意如果 JVM 宕机看门狗线程直接停止锁会等待过期自动释放不会永久死锁。手动指定 leaseTimeRedisson 认为用户自己管控过期不会启动看门狗业务执行时间不能超过 leaseTime。五、Redisson 代码演示1. maven 依赖dependency groupIdorg.redisson/groupId artifactIdredisson-spring-boot-starter/artifactId version3.17.7/version /dependency2. Redisson 配置类Configuration public class RedissonConfig { Bean public RedissonClient redissonClient(){ Config config new Config(); config.useSingleServer() .setAddress(redis://127.0.0.1:6379); return Redisson.create(config); } }3. 可重入锁业务代码Autowired private RedissonClient redissonClient; public void methodA(){ RLock lock redissonClient.getLock(lock:goods); try { // 不指定过期时间自动开启看门狗 lock.lock(); System.out.println(方法A获取锁成功); methodB(); //内部调用方法B再次获取同一把锁重入生效 }finally { if(lock.isHeldByCurrentThread()){ lock.unlock(); } } } public void methodB(){ RLock lock redissonClient.getLock(lock:goods); try { lock.lock(); System.out.println(方法B重入获取锁成功); }finally { if(lock.isHeldByCurrentThread()){ lock.unlock(); } } }调用methodA()A 拿到锁调用 BB 再次获取同一锁计数 1释放时 A、B 各 unlock 一次计数归零锁删除。tryLock 非阻塞获取锁// 最多等待10s锁持有30s这里手动指定持有时间看门狗失效 boolean success lock.tryLock(10,30, TimeUnit.SECONDS);六、Redisson 锁存在的问题主从架构锁失效Redis 主从同步是异步复制主节点执行 Lua 脚本加锁成功返回客户端锁数据还没同步到从节点主节点宕机从节点升级为新 master新主没有锁数据其他线程可以再次获取锁锁失效并发安全被破坏解决方案RedLock 红锁多独立 redis 节点过半节点加锁成功才算获取锁。缺点运维成本高生产极少使用。业务层兜底数据库唯一索引、乐观锁、幂等防止超卖、重复下单。七、面试高频问答Q1Redisson 可重入锁底层怎么实现底层使用 Redis Hash 结构存储锁field 存线程唯一标识value 存储重入计数。加锁、释放锁全部使用 Lua 脚本保证原子操作同一线程多次加锁计数累加多次释放计数递减计数归零才删除锁 key。Q2看门狗什么时候生效原理是什么调用不带过期时间的lock.lock()时自动开启默认 30s 过期每 10s 刷新锁过期时间。业务执行完毕释放锁看门狗停止。JVM 宕机看门狗停止锁到期自动释放。Q3tryLock 指定 leaseTime看门狗还会工作吗不会。手动指定锁过期时间Redisson 认为开发者自行管理锁生命周期不会启动看门狗续期。业务执行时长不能超过 leaseTime。Q4为什么释放锁前要判断 isHeldByCurrentThread ()如果业务异常锁已经过期自动释放其他线程拿到锁。此时执行 unlock 会释放别人的锁isHeldByCurrentThread()校验锁持有者只有当前线程持有锁才允许解锁。Q5Redisson RLock 是公平锁吗默认是非公平锁也支持公平锁getFairLock()底层用 list 队列记录等待线程按照先来后到获取锁性能比非公平锁差。八、总结Redisson 可重入锁底层基于 Redis Hash Lua 脚本实现支持重入计数看门狗自动续期解决业务执行时间超过锁过期时间的问题加锁、释放全程 Lua保证命令原子性避免手写锁的各种坑主从异步复制场景下依然存在锁失效风险核心业务需要数据库兜底开发优先使用 Redisson不要手写 Redis 分布式锁。