面试记录 2026-10-08
题目:实现一个分布式锁需要考虑哪些问题?
难度:medium
标签:arch/distributed/lock, distributed/lock, difficulty/medium
评分:⭐⭐⭐⭐☆(4/5)
我的回答
分布式锁要考虑哪些问题?锁要考虑性能,性能要高一些,一致性。锁的核心也就是一致性嘛,要保证几个线程看到的同一时间的是同样的。一致性对应的还有可用性。但是锁一般保证的是强一致,所以根据 CAP,它会放弃一定的可用性。锁如果出现问题了需要做补偿。另外就是可重入。一个锁在连续锁的情况下,根据单点锁生成的原理要考虑可重入,加几次锁要保证锁是一致的,同一个锁而不是多个锁。然后是加锁要考虑死锁问题,要定期的释放,比如使用 Redis 锁的话,要给锁进行释放,设置过期时间。另外就是如果锁内的持续事务没有完成,要考虑锁的续期。基本上就是这些了。
点评
优点:六维系统思考(性能/一致性/CAP/补偿/可重入/死锁/续期);CAP 权衡理解到位;区分死锁防护与锁续期。
不足:未提锁持有者校验(防误删);未提主从切换丢锁风险;"补偿"表述模糊(锁失败应走重试/幂等兜底而非补偿);遗漏锁粒度、公平性、可观测性;未提选型建议。
补充:安全性 checklist——原子性/持有者标识/过期时间/watchdog 续期/可重入计数;主流方案对比 Redis(AP+watchdog)vs ZK(CP+临时节点)vs etcd(Raft+Lease)。
完整讲解
第一部分:知识重点剖析
核心考点定位:考察分布式锁的工程化 checklist——不是"能不能加锁",而是"能不能安全地加锁"。
知识点拆解:
- 正确性维度:
- 互斥性:同一时间只有一个持有者
- 持有者校验:value 存 UUID/线程 ID,解锁前用 Lua 脚本校验
if value == mine then DEL,防误删别人的锁
- 加解锁原子性:
SET key value NX PX 30000 一条命令原子加锁;解锁用 Lua 脚本"校验+删除"原子执行
- 可重入:同线程多次加锁累加计数器,
count++ 每次续期,释放时 count-- 归零才真正删除
- 可靠性维度:
- 过期时间:防持锁进程崩溃导致死锁
- watchdog 续期:Redisson 每 TTL/3 检查持锁线程,存活则续期,崩溃则不续期让锁自然过期
- 主从切换风险(Redis 特有):主库加锁崩溃,从库升主但没同步到锁 key,第二个客户端能拿到锁;ZK/etcd 基于强一致集群无此风险
- RedLock 争议:Antirez 提出多节点多数派方案,但 Marko Elezi 论证客户端时钟偏移会导致失效,主流不推荐
- 可用性维度:
- 锁粒度:按业务对象锁(
lock:order:{orderId})而非全局锁,减少竞争
- 公平性:FIFO 排队 vs 抢占式,Redisson 默认非公平
- 性能瓶颈:单点 Redis 会成为瓶颈,热点 key 需要拆分(如 hash slot)
- 可观测性:锁等待时间、超时率、加解锁成功率要监控告警
- CAP 权衡:
- 锁一般选 CP(一致性优先),牺牲可用性(Redis 主库挂可能拒绝写入)
- Redis 是 AP 但通过业务幂等兜底降级到"最终一致"
按「正确性 → 可靠性 → 可用性 → CAP 权衡」四层递进。
第二部分:考察方向与延伸递进
考察方向:
- 深度:能否说清"加解锁原子性"和"持有者校验"的具体实现(Lua 脚本)
- 广度:对比 Redis/ZK/etcd/DB 在一致性/性能/死锁友好度上的差异
- 应用:业务侧如何为 Redis 锁的丢锁风险兜底(幂等设计)
延伸递进面试题:
- Redis 分布式锁的解锁 Lua 脚本怎么写?为什么不能分开执行 GET + DEL?
- 解答要点:
if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end;分开执行会有竞态——GET 校验通过但 DEL 之前锁刚好过期被其他客户端抢占,DEL 会误删别人的锁
- watchdog 机制具体如何工作?如果持锁线程 hang 死了怎么办?
- 解答要点:Redisson 启动独立看门狗线程,每 TTL/3 检查持锁状态;业务线程 hang 时 watchdog 仍能续期,业务侧需要有超时兜底(如业务线程设置超时时间);业务进程崩溃 watchdog 也停,锁自然过期
- 分布式锁和幂等控制是什么关系?是重复的吗?
- 解答要点:分布式锁解决"并发互斥"(同时只能一个进),幂等解决"多次执行结果一致"(进了多次也要一致);两者互补——锁在入口防止重复进入,幂等在业务层兜底;生产实践是"锁 + 幂等"组合使用
第三部分:面试者参考回答(300-600 字口语化)
面试官您好,实现一个分布式锁,我会从正确性、可靠性、可用性、CAP 权衡四个维度去考虑。
正确性层面:
- 加锁原子性:
SET key value NX PX 30000 一条命令原子执行"不存在才设置 + 过期时间";
- 持有者校验:value 存请求者的 UUID 或线程 ID,解锁必须校验是"自己的锁",防误删别人的锁;解锁用 Lua 脚本
if GET == mine then DEL 保证"校验 + 删除"原子;
- 可重入:同线程多次加锁累加计数器,count 归零才真正删除;
- 锁粒度:按业务对象加锁,比如
lock:order:{orderId} 而不是全局锁。
可靠性层面:
- 过期时间:防持锁进程崩溃导致死锁;
- watchdog 续期:Redisson 每 TTL/3 检查持锁线程是否存活,存活续期、崩溃不续;
- 主从切换丢锁(Redis 特有):主库加锁崩溃、从库升主没同步到锁 key,第二个客户端能加锁——这是 Redis 锁最致命的风险。兜底方案是业务侧做幂等,接受 AP 弱一致性;如果对一致性要求极高,用 ZooKeeper(临时顺序节点 + 强一致集群)或 etcd(Raft + Lease)。
可用性层面:
- 锁粒度设计避免过度串行;
- 可观测性:锁等待时间、超时率、加解锁成功率监控告警;
- RedLock 争议:Antirez 提过多节点多数派方案,但存在时钟偏移问题,主流社区已经不推荐。
CAP 权衡:
- 锁本质选 CP 一致性优先(宁可短暂不可用也不能多持锁);
- Redis 是 AP 弱一致,通过业务幂等兜底降级到"最终一致";
- 高并发业务(秒杀、库存)用 Redis + Redisson,Leader 选举用 ZK/etcd,低频场景用 DB。
实际项目中通常是分布式锁 + 业务幂等组合使用——锁挡并发重复进入,幂等兜底防止极端情况下重复执行。