☰
Redisson 可重入分布式锁底层原理详解
2026/10/10 1:21:33 网站建设 项目流程

一、什么是可重入锁?

可重入:同一个线程,在已经持有锁的前提下,可以再次获取同一把锁,不会出现自己把自己阻塞死的情况。

本地锁中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" -> 1

key:锁名称
field:标记哪个线程持有这把锁
value:重入次数,每重入一次 count+1,释放一次 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]:锁 key
  • ARGV[1]:锁过期时间(毫秒)
  • ARGV[2]:当前线程唯一标识(uuid:threadId)
流程解读
  1. 如果锁不存在:创建 hash,计数置 1,设置过期时间,加锁成功
  2. 如果锁存在,并且是当前线程的锁:hincrby计数 + 1,刷新过期时间,重入成功
  3. 如果锁存在,但属于别的线程:返回锁剩余有效期,加锁失败

三、释放锁 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. 不是当前线程的锁:直接返回,禁止释放别人的锁
  2. 计数 - 1
  3. 计数 > 0:还有重入,锁不删除,更新过期时间
  4. 计数 = 0:删除锁 key,锁完全释放

四、看门狗 WatchDog 自动续期机制(重点面试题)

什么时候开启看门狗?

当你调用lock.lock()(不指定过期时间),Redisson 会自动启动看门狗;

如果手动指定 leaseTime,看门狗不会自动开启,需要自己处理锁超时风险。

默认规则:

  • 锁默认过期时间:30s
  • 看门狗定时任务:每 10s 执行一次(过期时间的 1/3)
  • 定时任务逻辑:检查锁是否还被当前线程持有,持有则刷新锁过期时间回到 30s

原理:获取锁成功后,开启一个异步定时线程,不断延长锁有效期。只要业务没执行完毕,锁就不会过期;业务执行结束,锁释放,看门狗任务停止。

⚠️注意:

  • 如果 JVM 宕机,看门狗线程直接停止,锁会等待过期自动释放,不会永久死锁。
  • 手动指定 leaseTime,Redisson 认为用户自己管控过期,不会启动看门狗,业务执行时间不能超过 leaseTime。

五、Redisson 代码演示

1. maven 依赖

<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.17.7</version> </dependency>

2. 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 拿到锁,调用 B,B 再次获取同一锁,计数 + 1;释放时 A、B 各 unlock 一次,计数归零,锁删除。

tryLock 非阻塞获取锁

// 最多等待10s,锁持有30s,这里手动指定持有时间,看门狗失效 boolean success = lock.tryLock(10,30, TimeUnit.SECONDS);

六、Redisson 锁存在的问题:主从架构锁失效

Redis 主从同步是异步复制:

  1. 主节点执行 Lua 脚本,加锁成功,返回客户端
  2. 锁数据还没同步到从节点,主节点宕机
  3. 从节点升级为新 master,新主没有锁数据
  4. 其他线程可以再次获取锁,锁失效,并发安全被破坏

解决方案:

  1. RedLock 红锁:多独立 redis 节点,过半节点加锁成功才算获取锁。缺点:运维成本高,生产极少使用。
  2. 业务层兜底:数据库唯一索引、乐观锁、幂等,防止超卖、重复下单。

七、面试高频问答

Q1:Redisson 可重入锁底层怎么实现?

底层使用 Redis Hash 结构存储锁,field 存线程唯一标识,value 存储重入计数。加锁、释放锁全部使用 Lua 脚本保证原子操作,同一线程多次加锁计数累加,多次释放计数递减,计数归零才删除锁 key。

Q2:看门狗什么时候生效?原理是什么?

调用不带过期时间的lock.lock()时自动开启;默认 30s 过期,每 10s 刷新锁过期时间。业务执行完毕释放锁,看门狗停止。JVM 宕机,看门狗停止,锁到期自动释放。

Q3:tryLock 指定 leaseTime,看门狗还会工作吗?

不会。手动指定锁过期时间,Redisson 认为开发者自行管理锁生命周期,不会启动看门狗续期。业务执行时长不能超过 leaseTime。

Q4:为什么释放锁前要判断 isHeldByCurrentThread ()?

如果业务异常,锁已经过期自动释放,其他线程拿到锁。此时执行 unlock 会释放别人的锁,isHeldByCurrentThread()校验锁持有者,只有当前线程持有锁才允许解锁。

Q5:Redisson RLock 是公平锁吗?

默认是非公平锁;也支持公平锁getFairLock(),底层用 list 队列记录等待线程,按照先来后到获取锁,性能比非公平锁差。

八、总结

  1. Redisson 可重入锁底层基于 Redis Hash + Lua 脚本实现,支持重入计数;
  2. 看门狗自动续期,解决业务执行时间超过锁过期时间的问题;
  3. 加锁、释放全程 Lua,保证命令原子性,避免手写锁的各种坑;
  4. 主从异步复制场景下,依然存在锁失效风险,核心业务需要数据库兜底;
  5. 开发优先使用 Redisson,不要手写 Redis 分布式锁。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询