一、基础概念类
Q1:什么是分布式锁?为什么需要?
分布式环境下,多个进程/节点互斥访问共享资源。
单机锁(synchronized、ReentrantLock)只在单 JVM 有效。
分布式锁保证跨节点互斥。
场景:库存扣减、订单防重、定时任务防重复执行。
Q2:分布式锁有哪些实现方式?
Q3:分布式锁要满足哪些条件?
互斥:同一时刻只有一个持有者。
防死锁:持有者挂了锁能释放,需过期时间。
防误删:只能删自己的锁。
可重入(可选):同一线程可重复获取。
高可用:锁服务不能单点。
二、SETNX 基础实现类
Q4:怎么用 Redis 实现分布式锁?
SET lock_key unique_value NX EX 30NX:key 不存在才设置。EX 30:30 秒过期,防死锁。unique_value:唯一标识,防误删。
Q5:为什么用 SET NX EX 而不是 SETNX + EXPIRE?
SETNX+EXPIRE两条命令非原子。如果
SETNX后宕机,EXPIRE没执行,锁永不过期,死锁。SET NX EX一条命令原子完成。这是必须用
SET的原因。
Q6:value 为什么用唯一标识?
防止误删别人的锁。
场景:A 持锁,超时释放,B 拿到锁,A 执行完删锁,删了 B 的锁。
value 存 UUID + 线程 ID,删除前校验。
Q7:怎么正确删锁?
用 Lua 脚本保证原子性:
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end先校验 value 是否是自己,再删。
校验和删除必须原子,否则仍有误删窗口。
Q8:为什么删锁要用 Lua?
GET+DEL两条命令非原子。中间可能锁过期、别人拿到锁,导致误删。
Lua 在 Redis 中原子执行,避免窗口。
Q9:锁过期了业务没执行完怎么办?
需要锁续期(看门狗)。
后台线程定期检查,业务没完就延长 TTL。
Redisson 的看门狗默认每 10 秒续期到 30 秒。
Q10:锁的过期时间怎么设?
要大于业务执行时间。
太短:业务没完锁就释放,并发问题。
太长:持有者挂了,其他等待久。
配合续期机制更稳。
三、Redisson 类
Q11:Redisson 是什么?
Java 的 Redis 客户端,封装了分布式锁。
提供
RLock,支持可重入、自动续期、阻塞等待。底层用 Lua 脚本保证原子性。
Q12:Redisson 的可重入怎么实现?
用 Hash 结构存锁:
key -> {uuid:threadId -> count}。同一线程再次加锁,count + 1。
释放时 count - 1,减到 0 才删 key。
用 Lua 保证原子。
Q13:Redisson 的看门狗是什么?
加锁成功后,启动后台线程定时续期。
默认
lockWatchdogTimeout=30s,每 10s 续到 30s。业务没完就不断续期。
业务结束或宕机,不再续期,锁自动过期。
Q14:看门狗在什么情况下不生效?
用
lock()无参才启用看门狗。用
lock(10, TimeUnit.SECONDS)指定时间,不启用看门狗。需手动处理续期或确保业务在 TTL 内完成。
Q15:Redisson 加锁流程?
用 Lua 脚本尝试
HSET锁,设置 TTL。成功则返回,启动看门狗。
失败则订阅锁释放消息,等待重试。
释放时减 count,为 0 删 key,发布消息唤醒等待者。
Q16:Redisson 怎么处理锁等待?
用 Redis 的 Pub/Sub,锁释放时通知等待者。
避免轮询,减少无效请求。
等待超时可配。
四、Redlock 类
Q17:什么是 Redlock?
Redis 作者提出的多节点分布式锁算法。
部署 N 个独立 Redis 主节点(通常 5 个)。
客户端依次向所有节点加锁。
多数派成功且总耗时小于锁有效期,才算加锁成功。
Q18:Redlock 的加锁流程?
记录开始时间。
依次向 N 个节点用相同 key/value 加锁,设超时。
如果多数派(N/2+1)成功,且总耗时 < 锁 TTL,则成功。
否则向所有节点解锁,重试。
Q19:Redlock 有什么争议?
Martin Kleppmann 质疑:
依赖系统时钟,时钟漂移可能破坏互斥。
没有 fencing token,GC 停顿可能导致旧持有者仍写。
Redis 作者 Antirez 反驳:
时钟漂移可用漂移因子处理。
不是所有场景都需要 fencing。
结论:Redlock 有争议,强一致场景慎用。
Q20:什么是 fencing token?
每次加锁生成递增的 token。
写资源时带上 token,存储端拒绝旧 token。
解决“锁过期但旧持有者仍在写”的问题。
Redlock 本身不提供,需自行实现。
Q21:Redlock 和单节点锁怎么选?
单节点:简单,但 Redis 挂了锁不可用。
主从 + 哨兵:故障转移可能丢锁。
Redlock:多节点,但复杂且有争议。
一般业务用 Redisson 单节点/哨兵即可,强一致用 ZooKeeper/etcd。
Q22:主从架构下锁会丢吗?
会。主库加锁后未同步,主库挂了,从库提升,锁丢失。
其他客户端可再次加锁,互斥被破坏。
Redlock 或
WAIT可缓解。
五、常见问题与坑类
Q23:锁超时导致并发问题怎么解决?
看门狗续期。
业务异步化,快速释放锁。
fencing token 兜底。
评估业务执行时间,合理设 TTL。
Q24:Redis 主从切换锁丢失怎么办?
用 Redlock。
加锁后
WAIT等从库确认。强一致用 ZooKeeper/etcd。
业务层幂等兜底。
Q25:分布式锁能保证绝对互斥吗?
不能绝对。
网络分区、时钟漂移、GC 停顿都可能导致。
要配合幂等、fencing token。
锁是减少并发,不是绝对安全。
Q26:锁等待怎么实现?
轮询:简单但浪费。
Pub/Sub:Redisson 用,锁释放通知。
阻塞队列:本地等待。
Q27:公平锁怎么实现?
Redisson 支持
RedissonFairLock。用有序集合记录等待顺序。
按顺序获取锁。
性能低于非公平锁。
Q28:读写锁怎么实现?
Redisson
RReadWriteLock。读锁共享,写锁互斥。
用 Hash 记录读锁和写锁状态。
Q29:锁的 key 怎么设计?
按资源命名,如
lock:order:123。加业务前缀,避免冲突。
不要用固定 key,否则所有请求竞争一把锁。
Q30:锁的 value 怎么设计?
UUID:threadId,标识持有者。支持可重入和误删校验。
不要用固定值。
六、实战与选型类
Q31:Redisson 和手写 SETNX 怎么选?
生产用 Redisson:可重入、续期、等待、成熟。
手写适合简单场景,但要注意原子性和误删。
不要重复造轮子。
Q32:Redis 锁和 ZooKeeper 锁怎么选?
Q33:库存扣减用分布式锁合适吗?
可以,但性能差。
更好用 Redis 原子操作(
DECR)或 Lua。或数据库乐观锁。
锁是兜底,不是首选。
Q34:分布式锁和幂等的关系?
锁防并发,幂等防重复。
锁可能失效,幂等兜底。
重要操作两者都要。
Q35:怎么测试分布式锁?
并发压测,验证互斥。
模拟锁超时、主从切换。
验证误删、死锁。
监控锁等待和超时。
七、高频陷阱题
Q36:SETNX 成功后宕机,锁会死吗?
如果只
SETNX没EXPIRE,会死锁。用
SET NX EX原子设置,不会。所以必须用
SET。
Q37:锁过期后业务还在执行,会怎样?
其他客户端可获取锁,并发执行。
可能导致数据不一致。
解决:续期、fencing token、幂等。
Q38:DEL 锁时没校验 value 会怎样?
可能删别人的锁。
A 超时释放,B 拿锁,A 删了 B 的锁。
必须 Lua 校验 value。
Q39:Redlock 能保证强一致吗?
不能绝对。有时钟漂移、GC 问题。
争议较大。
强一致用 ZooKeeper/etcd。
Q40:看门狗一直续期会死锁吗?
客户端宕机,看门狗停止,锁到期释放。
不会死锁。
但客户端假死(GC 停顿)可能续期,业务却没执行。
Q41:Redis 锁能重入吗?
原生
SET NX不能。Redisson 用 Hash 实现可重入。
手写要自己维护 count。
Q42:锁的 TTL 设为 -1 会怎样?
永不过期,持有者挂了就死锁。
必须设正数 TTL。
配合续期。
评论