Redisson 分布式锁复习总结
Redisson 分布式锁复习总结
一、 为什么需要 Redisson?(基于 setnx 的问题)
手动实现的 setnx 锁存在四大痛点,Redisson 逐一解决:
| 痛点 | 描述 | Redisson 解决方案 |
|---|---|---|
| 不可重入 | 同一个线程无法多次获取同一把锁,导致嵌套调用死锁。 | Hash 结构存储计数器,支持同一线程多次加锁。 |
| 不可重试 | 获取锁失败直接返回,无重试机制。 | tryLock 方法支持等待时间,内部通过订阅与自旋实现重试。 |
| 超时释放 | 业务执行时间 > 锁过期时间,导致锁自动释放,安全隐患。 | WatchDog(看门狗)机制,自动续期,默认每 10s 续约至 30s。 |
| 主从一致性 | 主节点写入锁后宕机,未同步到从节点,导致锁丢失。 | MultiLock(联锁),将多个 Redis 节点视为独立节点,须全部加锁成功。 |
二、 快速入门与基本使用
1. 依赖与配置
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.150.101:6379").setPassword("123321");
return Redisson.create(config);
}
}
2. 核心代码模板
RLock lock = redissonClient.getLock("lock:order:" + userId);
// 尝试获取锁
// boolean isLock = lock.tryLock(); // 无参:立即返回
boolean isLock = lock.tryLock(1, 10, TimeUnit.SECONDS); // 有参:等待1s,锁自动释放10s(WatchDog失效)
if (isLock) {
try {
// 执行业务逻辑
} finally {
lock.unlock(); // 释放锁
}
}

三、 核心原理:可重入锁
1. 实现原理
Redisson 放弃了 setnx 的简单 String 结构,改用 Redis Hash 结构。
- Key:锁名称(如
lock:order:1) - Field:线程标识(
UUID + ThreadID) - Value:重入次数(计数器)
2. Lua 脚本逻辑 (加锁)
Redisson 使用 Lua 脚本保证原子性:
- 锁不存在:创建 Hash,
hset字段值为 1,设置过期时间。返回null(成功)。 - 锁存在且是自己:
hincrby将计数器 +1,重置过期时间。返回null(成功)。 - 锁存在但不是自己:返回锁的剩余生存时间(失败)。
3. 解锁逻辑
- 将计数器
-1。 - 若计数器
> 0:说明还有重入,保留锁,重置过期时间。 - 若计数器
<= 0:删除 Key,发布解锁消息。
分布式锁原理
🧩 Redisson 分布式锁核心流程拆解
我帮你把这张流程图和对应的源码逻辑一步步拆开讲,你就能看懂每个判断和分支的意义了。

1. 开始:尝试获取锁
入口是 RLock.lock() 或 RLock.tryLock() 方法,本质是向 Redis 发送 SET 命令尝试创建锁键(比如 redisson_lock:key)。
2. 判断 ttl 是否为 null
- ttl = null:代表获取锁成功(锁键不存在,成功写入)
- ttl ≠ null:代表获取锁失败(锁已被其他线程持有,返回当前锁的剩余过期时间)
对应源码:
tryLockInnerAsync()方法,执行SET命令后,返回null表示成功,返回ttl表示失败。
3. 分支1:获取锁成功(ttl = null)
- 判断
leaseTime是否为-1-
是(leaseTime = -1):开启 watchDog(看门狗) 自动续期机制
Redisson 会启动一个后台定时任务,每隔lockWatchdogTimeout / 3时间(默认 10s/3≈3.3s),给锁键重置过期时间(默认 30s),防止业务执行时间过长导致锁提前过期。leaseTime=-1 = 永久自动续期,直到解锁或服务挂掉。
不管业务是运行、阻塞、休眠,都续期。(超时续约) -
否(leaseTime > 0):不开启看门狗,锁会在
leaseTime后自动过期
-
- 最终返回
true,表示加锁成功
对应源码:
lock()方法默认leaseTime = -1,会自动续期;tryLock(waitTime, leaseTime, unit)手动指定leaseTime则不会续期。
4. 分支2:获取锁失败(ttl ≠ null)
进入等待重试逻辑:
- 判断剩余等待时间是否 > 0
- 否:直接返回
false,表示加锁失败 - 是:订阅 Redis 的
pub/sub通道,等待锁释放的信号
- 否:直接返回
- 订阅后,阻塞等待锁释放通知
- 等待过程中会不断检查等待时间是否超时
- 超时:取消订阅,返回
false - 未超时:收到锁释放信号后,回到「尝试获取锁」步骤,重新竞争锁
- 超时:取消订阅,返回
- 等待过程中会不断检查等待时间是否超时
对应源码:
subscribe()方法订阅锁释放频道,await()阻塞等待信号,超时后通过cancel()取消订阅并返回失败。
💡 关键源码概念对应
| 流程图节点 | 源码对应 | 核心作用 |
|---|---|---|
ttl 是否为 null |
tryLockInnerAsync() 返回值 |
标识锁是否获取成功 |
leaseTime 是否为 -1 |
lock() 方法参数 |
控制是否开启自动续期 |
开启 watchDog |
RenewExpirationTask 定时任务 |
后台续期,防止锁提前过期 |
订阅并等待释放锁的信号 |
LockPubSub 发布订阅 |
避免自旋浪费 CPU,高效等待锁 |
判断等待时间是否超时 |
await(timeout, unit) |
控制最大等待时间,防止无限阻塞 |
✅ 完整流程总结
- 尝试加锁 → 成功则根据
leaseTime决定是否续期 → 返回true - 加锁失败 → 若还有等待时间 → 订阅锁释放信号 → 等待唤醒后重试
- 等待超时或无剩余等待时间 → 返回
false,加锁失败
❓ 你可能看不懂的点补充
- watchDog 为什么只在
leaseTime=-1时开启?
手动指定leaseTime代表你能预估业务执行时间,Redisson 认为不需要自动续期,避免锁长期占用。 - 订阅信号比自旋好在哪?
自旋会不断发送GET/SET命令,浪费 CPU 和 Redis 资源;订阅模式只有锁释放时才唤醒,更高效。 - ttl 是什么?
time to live,Redis 键的剩余过期时间,Redisson 用它判断锁是否还在被持有。
🔓 Redisson 释放锁流程完整解析
把右边释放锁的流程和左边加锁的联动逻辑讲透,你就能看懂整个闭环了。
1. 入口:尝试释放锁
调用 RLock.unlock() 方法,本质是执行 Redis Lua 脚本:
- 校验当前线程是否持有该锁(防止误删别人的锁)
- 删除锁键
- 触发后续清理逻辑
2. 判断是否成功
- 是:释放锁成功(Lua 脚本执行完成,锁已删除)
- 否:释放锁失败(比如锁已过期、不是自己持有的锁),记录异常后结束
对应源码:
unlockInnerAsync()方法,通过 Lua 脚本原子性完成「校验+删除」。
3. 释放成功后的核心动作
释放锁成功后,会触发两个关键操作:
① 取消 watchDog(看门狗)
- 停止后台自动续期的定时任务
- 避免锁已经释放了,还在白白刷新过期时间
- 这一步和左边加锁流程里的「开启 watchDog」是生命周期配对的
② 发送释放锁的消息(Pub/Sub)
- 向 Redis 的
lock频道发布「锁已释放」的通知 - 唤醒所有正在订阅这个锁的等待线程(左边流程里「订阅并等待释放锁的信号」的线程)
- 等待线程收到通知后,会重新进入「尝试获取锁」的竞争环节
4. 与加锁流程的联动
这张图里的绿色虚线就是关键联动:
- 释放锁成功 → 取消当前持锁线程的 watchDog
- 释放锁成功 → 发送通知,唤醒正在等待的线程,让它们重新尝试获取锁
- 等待线程被唤醒后,回到「尝试获取锁」的起点,继续竞争
💡 关键源码概念对应
| 流程图节点 | 源码对应 | 核心作用 |
|---|---|---|
尝试释放锁 |
unlock() / unlockInnerAsync() |
执行 Lua 脚本释放锁 |
判断是否成功 |
Lua 脚本返回值 | 校验锁归属与删除结果 |
取消 watchDog |
cancelExpirationRenewal() |
停止自动续期任务 |
发送释放锁消息 |
publishUnlock() |
发布 Pub/Sub 通知唤醒等待者 |
记录异常 |
日志/抛出 IllegalMonitorStateException |
处理非法释放锁的场景 |
✅ 完整释放锁流程总结
- 调用
unlock()→ 执行 Lua 脚本校验并删除锁 - 释放成功:
- 停止看门狗续期任务
- 发布锁释放通知,唤醒等待线程
- 结束
- 释放失败:
- 记录异常(比如「锁不属于当前线程」)
- 结束
❓ 你可能关心的细节补充
- 为什么用 Lua 脚本?
保证「判断锁归属 + 删除锁」是原子操作,避免并发问题。 - 为什么要发 Pub/Sub 消息?
替代自旋等待,让等待线程在锁释放时才被唤醒,更高效。 - 如果服务宕机了,锁会怎样?
看门狗随服务一起停止,锁会在lockWatchdogTimeout(默认30秒)后自动过期,避免死锁。
四、 进阶原理:锁重试与 WatchDog
1. WatchDog (看门狗机制)
目的:解决业务执行时间过长导致锁超时释放的问题。
- 触发条件:调用
lock()或tryLock()不带 LeaseTime 参数时。 - 默认配置:锁过期时间
30s,检查间隔10s。 - 工作流程:
- 加锁成功后,启动一个后台定时任务。
- 每隔
10s(过期时间/3)检查锁是否还被当前线程持有。 - 若持有,重置过期时间为
30s(续约)。 - 若线程宕机,看门狗停止,锁等待
30s后自动释放。
2. 锁重试机制
tryLock(waitTime, leaseTime, unit):- 如果设置了
leaseTime,看门狗机制失效,锁会在指定时间后释放。 - 获取失败时,会利用 Redis 的 Pub/Sub(发布订阅) 机制等待锁释放通知,而不是纯粹的死循环自旋(性能更优)。
- 如果设置了
五、 高可用原理:MultiLock (联锁)
1. 问题背景
在 Redis 主从架构中:
- 客户端向 Master 写入锁。
- Master 宕机,数据未同步到 Slave。
- Slave 升级为新 Master,锁数据丢失。
- 其他客户端成功获取锁,导致并发安全问题。
2. MultiLock 解决方案
原理:放弃主从一致性,将多个 Redis 节点视为独立的 Master。
- 加锁时:必须向所有节点都发送加锁请求,全部成功才算加锁成功。
- 容错性:只要有任意一个节点存活,锁就不会丢失(类似 CP 模型)。
代价:性能下降(需要连接多个节点并发写入)。
六、 复习口诀
- 重入:Hash 结构存 ID,计数加减保原子。
- 续约:看门狗,定时跑,十秒一刷三十秒。
- 重试:消息订阅等通知,避免空转耗资源。
- 高可用:主从同步怕宕机,联锁多写保安全。
更多推荐


所有评论(0)