设计一个分布式锁需要满足哪些条件?
简化版
一个合格的分布式锁至少要满足:① 互斥性(任意时刻只有一个客户端持锁)、② 防死锁(持锁者宕机也能自动释放,靠过期时间/临时节点)、③ 谁加锁谁释放(不能误删别人的锁,靠唯一标识)、④ 加解锁原子性(用一条命令/Lua/事务,避免中间态)、⑤ 高可用高性能(锁服务不能单点、加解锁要快)。进阶还包括可重入、阻塞等待、公平性、锁续期等。
详细版
五个基本条件:
- 互斥:这是锁的根本——同一时刻最多一个客户端持有。靠 Redis 的 NX、数据库唯一索引、ZK 节点创建的排他性实现。
- 防死锁:持锁客户端如果宕机、没来得及解锁,锁必须能被自动释放,否则其他人永远拿不到。Redis 靠过期时间,ZK 靠临时节点会话机制。
- 谁加锁谁释放(防误删):一个客户端不能释放/删除另一个客户端持有的锁。靠锁里存客户端唯一标识(UUID+线程ID),解锁前校验身份。
- 加解锁的原子性:加锁(占坑 + 设过期)、解锁(校验 + 删除)都必须原子,否则中间态会出问题(如只占坑没设过期就崩 → 死锁)。用
SET NX EX一条命令、Lua 脚本、数据库事务保证。 - 高可用 + 高性能:锁服务本身不能是不可用的单点,加解锁延迟要低(否则拖慢业务)。
进阶特性:可重入(同一持有者可多次加锁)、阻塞等待(拿不到锁能排队而非直接失败)、公平性(按申请顺序获得)、锁续期(业务超时自动续命)。
完整版教学
一、五个基本条件逐一为什么重要
互斥是锁存在的意义,不多说。重点看其余四个「容易被忽略但缺了就出事」的条件:
防死锁——分布式里客户端随时可能宕机。如果锁没有「自动释放」机制,一个持锁者崩溃就会让锁永远被占,后续所有请求卡死。这是为什么 Redis 锁必须设过期时间、ZK 锁要用临时节点。**「一定要有一个不依赖持锁者主动解锁的兜底释放机制」**是硬要求。
谁加锁谁释放——如果不校验身份,会出现「A 的锁过期被 B 拿到,A 苏醒后把 B 的锁删了」的误删(见误删专题)。所以锁必须能识别持有者身份,解锁时验明正身。
原子性——加锁的「占坑 + 设过期」如果分两步(SETNX + EXPIRE),中间崩溃就死锁;解锁的「校验 + 删除」如果分两步,中间锁到期被抢就误删。所以关键操作必须原子。
高可用高性能——锁是很多请求的必经之路,如果锁服务慢或不可用,整个系统被拖垮。所以要用 Redis/ZK 集群保证可用,用高效的实现保证低延迟。
二、这五个条件如何映射到具体实现
以 Redis 锁为例,一条 SET key uniqueValue NX EX 30 + Lua 解锁,恰好覆盖:
- 互斥 ←
NX - 防死锁 ←
EX 30 - 谁加谁释放 ←
uniqueValue+ 解锁校验 - 原子性 ←
SET ...NX EX一条命令 + Lua 解锁 - 高可用高性能 ← Redis 集群 + 内存操作
一道题就能看出「为什么标准 Redis 锁要这么写」——每个部分都对应一个必须满足的条件。
记忆点:把标准 Redis 锁的写法拆开看,NX(互斥)、EX(防死锁)、唯一 value(防误删)、Lua(原子)、集群(高可用)——五个基本条件一一对应,不是随便写的。
三、进阶特性:什么时候需要
- 可重入:方法嵌套/递归会重复获取同一把锁,不可重入会自锁死。绝大多数生产锁(Redisson)默认可重入。
- 阻塞等待:拿不到锁时,是直接返回失败(tryLock)还是排队等待(lock)?看业务——秒杀可能直接失败快速返回,普通业务可能需要等待。
- 公平性:是否按申请先后顺序获得锁。ZK 天然公平;Redis 默认非公平(要额外实现)。公平性能防「饥饿」(某客户端一直抢不到),但会牺牲一点吞吐。
- 锁续期:业务执行时间不确定时,需要看门狗自动续期,防止锁提前过期(见 Redisson 看门狗专题)。
四、一个容易忽略的点:锁的粒度
设计分布式锁还要考虑锁粒度:锁太粗(如锁整个「下单」操作),会让所有订单串行、并发极低;锁太细/错误(如没锁对资源)又起不到保护作用。正确做法是锁住具体的资源——比如按商品 ID 锁(lock:stock:商品123),不同商品的下单可以并行,只有同一商品的并发才互斥。粒度选择直接决定了系统的并发能力。
五、分布式锁落地自检
评估一把分布式锁是否合格,不要只看“能不能加锁成功”,而要按故障路径压测:客户端加锁后宕机、业务执行超过 TTL、释放锁时网络抖动、Redis 主从切换、同一请求重复进入。每条路径都要能解释靠 TTL、owner 校验、Lua 原子释放、watchdog 或业务幂等中的哪一层兜底。
六、常见误区与追问
这道题不能只背概念,要把「分布式锁要求」放回真实分布式系统里解释:谁发起、谁协调、状态如何变化、失败后怎么兜底,以及它和性能、可用性、一致性的取舍。
| 回答层次 | 要讲清的内容 | 容易漏掉的边界 |
|---|---|---|
| 核心结论 | 合格分布式锁至少要互斥、防死锁、防误删、可续期、可重入可选、高可用和性能可接受 | 不要停在名词解释 |
| 流程机制 | 尝试原子加锁 -> 设置过期时间 -> 执行业务并必要续期 -> 释放时校验 owner -> 异常时靠 TTL 兜底 | 说明触发方、参与方、状态变化和兜底 |
| 工程取舍 | Redis 锁 value 用 requestId,释放时先比较再删除,避免 A 超时后删掉 B 的锁 | 分布式锁只能控制临界区进入,不能替代业务幂等和状态校验 |
分布式锁要求 面试拆解:
1. 尝试原子加锁
2. 设置过期时间
3. 执行业务并必要续期
4. 释放时校验 owner
5. 异常时靠 TTL 兜底
记忆钩子:先说为什么单机锁失效,再说明加锁、过期、续期、解锁、防误删和故障恢复;回答时要紧扣「分布式锁要求」这道题,不要把相邻概念混成一段泛泛的分布式套话。
- 误区:SETNX 成功就是合格锁。 还必须有过期时间、唯一标识、原子释放和异常兜底。
- 误区:锁过期时间随便设。 太短会业务未完成锁失效,太长会故障后阻塞恢复。
- 误区:谁都可以删除锁。 必须校验锁 value 是自己的 owner 标识后再释放。
- 追问:如何防死锁? 加锁和设置过期时间要原子完成,异常时 TTL 自动释放。
- 追问:如何支持可重入? 记录线程或请求 owner 与重入次数,释放时递减计数。
- 追问:如何处理长任务? 用 watchdog 自动续期或把任务拆短,但仍要考虑续期失败。
七、加强记忆
分布式锁五个基本条件:互斥(NX/唯一索引)、防死锁(过期时间/临时节点,不依赖持锁者主动解锁的兜底释放)、谁加锁谁释放(唯一标识防误删)、加解锁原子性(一条命令/Lua/事务)、高可用高性能(集群+低延迟)。标准 Redis 锁 SET key uuid NX EX + Lua 解锁恰好一一对应这五条。进阶特性有可重入、阻塞等待、公平性、续期。别忘了锁粒度——锁住具体资源(按 ID)而非整个操作,才能兼顾安全和并发。