🔐 分布式锁
SETNX+Lua 的正确姿势 · Redisson 看门狗 · 红锁 · 与数据库/ETCD 锁对比
1. 基于 Redis 实现分布式锁的正确姿势?
为什么需要分布式锁:多实例部署时 JVM 锁(synchronized)只锁本机,无法互斥跨实例的并发操作(如扣库存)。
完整要点(每个都是坑):
- 加锁要原子:SET key value NX EX 30 —— NX(不存在才设)+ EX(过期时间)一条命令,不能拆成 SETNX + EXPIRE 两步(中间崩溃锁永不释放)
- value 要唯一:UUID/线程 ID,用于释放时校验"是我的锁"
- 释放要 Lua 原子校验删除:GET 比对 value 一致才 DEL——不能先 GET 再 DEL(两步间锁可能已被他人重新获取,误删别人的锁)
- 过期时间:锁持有时间要设上限,防止持锁方崩溃后死锁
标准实现(Lua 保证原子性)
// 加锁(一条命令原子完成)
SET lock:order:100 uuid NX EX 30
// 释放(Lua 脚本:值匹配才删除,原子)
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
🎯 面试要点
- 三个"必须":加锁必须一条 SET NX EX、value 必须唯一、释放必须 Lua 校验
- 获取锁失败要重试(带退避),别空转;获取要设超时,别无限等
- 锁粒度:锁资源 ID(lock:order:100)而不是全局一把锁(锁粒度小并发高)
2. Redisson 的看门狗机制解决什么问题?
问题:业务执行时间 > 锁过期时间 → 锁提前释放 → 其他线程拿到锁 → 两个线程同时执行临界区。
看门狗(Watchdog):Redisson 加锁后启动后台定时任务,每 10 秒(锁 TTL 的 1/3)自动续期到 30 秒。只要持锁线程还活着,锁就不会过期;线程挂了,看门狗随线程终止,锁按 TTL 自动释放。自动续期 = 防死锁 + 防提前释放两全。
另外 Redisson 还支持:可重入(RLock)、公平锁、读写锁、红锁(RedissonMultiLock)。
RLock 标准用法
RLock lock = redisson.getLock("lock:order:100");
boolean locked = false;
try {
locked = lock.tryLock(3, 30, TimeUnit.SECONDS); // 等待 3s,锁 30s,看门狗续期
if (!locked) throw new RuntimeException("获取锁失败");
// 业务...
} finally {
if (locked) lock.unlock(); // 自动执行 Lua 校验删除
}
🎯 面试要点
- 看门狗的本质:定时续期 + 进程死亡自动释放(Redis 键过期机制兜底)
- 手动指定 leaseTime(锁时长)时看门狗不生效(不用续期)
- 面试延伸:锁续期失败(Redis 抖动)怎么办 → 业务做幂等兜底(数据库唯一约束)
3. 红锁(RedLock)?分布式锁的选型对比?
RedLock(红锁):单主节点锁的隐患是——主节点加锁成功但未同步到从节点就宕机 → 从节点升主 → 锁丢了,两个线程同时持锁。RedLock 思路:向 N/2+1 个独立节点依次加锁,多数成功才算持锁,多数加锁失败则回滚释放。代价:部署复杂、性能下降。
分布式锁选型对比:
| 方案 | 可靠性 | 性能 | 适用 |
|---|---|---|---|
| Redis SETNX | 中(主从切换可能丢锁) | 高 | 大多数业务场景 |
| ZooKeeper | 高(临时节点+ZAB 顺序) | 中(会话心跳) | 强一致性场景 |
| ETCD | 高(Raft + lease) | 中高 | 云原生/分布式配置 |
工程判断:Redis 锁(+幂等兜底)满足 99% 业务;对锁丢失零容忍(资金类)选 ZK/ETCD 或数据库唯一约束。不要为了理论完美把系统做复杂。
🎯 面试要点
- 经典追问"RedLock 是否真的安全"(作者 Martin Kleppmann 与 Antirez 论战):时钟跳跃、GC pause 都会让锁失效——知道这个争论是加分项
- 终极兜底:锁 + 幂等(唯一索引/状态机)双保险才是生产常态
- DB 实现锁:SELECT FOR UPDATE 或唯一索引插入,简单但性能差、有单点