← 返回分布式系统
🔐 分布式协调 · 面试必问

分布式锁

多台机器同时改同一份资源时,要有把"跨进程、跨机器"的锁。它比单机锁难在:加锁/解锁要原子、要能容错、要防死锁。

🎯 一、为什么需要分布式锁

单机里的 synchronized / ReentrantLock 只管一个 JVM 内的线程。当同一份数据被多个服务实例并发修改(超卖、重复对账、幂等防重),就需要一把所有实例都认的锁。

💥
典型场景:秒杀库存扣减、定时任务在多节点只跑一次、分布式事务防重提交。不加锁 → 超卖/重复执行。
一把合格的分布式锁要满足:互斥(任一时刻仅一个持有者)、不死锁(持有者挂了锁能释放)、谁加的锁谁解(防误删)、容错(锁服务本身高可用)。

🔴 二、Redis 分布式锁(最常用)

原子加锁:SET key uuid NX EX 30
一条命令同时完成"不存在才设"和"设置过期时间",避免"SETNX 后崩溃没设过期"导致的死锁。value 用唯一 uuid 标识自己是持有者。
安全解锁:Lua 脚本校验 uuid 再删
不能直接 DEL——否则可能删掉别人刚拿到的锁。必须用 Lua 原子地"判断 value==我的uuid 才删除"。
看门狗自动续期
业务没跑完锁就快过期?Redisson 的 watch dog 在持有期间每 1/3 超时周期自动续期,直到主动释放。避免"业务没完锁先没"。
⚠️
主从切换的隐患:主库刚写入锁就宕机,从库还没同步就被提升为主——此时另一客户端也能加锁成功,出现"双持锁"。这就是 Redis 官方 Redlock 想解决的,但 Redlock 在分布式学界仍有争议(Martin Kleppmann 批评其依赖时钟假设)。

🟢 三、ZooKeeper 分布式锁

利用 ZK 的临时顺序节点:客户端在锁目录下创建临时顺序节点,序号最小的获得锁;其余监听前一个节点,前一个释放(节点消失)后自己顺位获得。

/locks/order (持久父节点) 001← 持有锁 002监听001 003监听002
✅
优点:ZK 用临时节点保证"客户端宕机会话断开→节点自动消失→锁释放",天然防死锁,且靠顺序节点实现公平排队,比 Redis 在"强一致"上更稳。缺点:性能与吞吐不如 Redis,依赖 ZK 集群。

🗃️ 四、数据库锁(兜底方案)

① 唯一索引 / 悲观锁
插入一条带唯一键的记录表示获锁;或 SELECT ... FOR UPDATE 行锁。简单但 DB 成瓶颈,且连接断开才能释放,一般不作首选。
② 乐观锁(版本号 / CAS)
更新时带版本号 UPDATE ... SET val=? , version=version+1 WHERE id=? AND version=old,影响行数为 0 即失败重试。适合冲突少的场景,但不是"锁"而是"无锁并发控制"。

📊 五、三种方案对比

方案优点缺点适用
Redis性能极高、实现简单主从切换可能双持锁(弱一致)绝大多数高并发业务
ZooKeeper强一致、防死锁、公平吞吐较低、依赖 ZK对正确性要求极高
数据库零额外组件性能差、易成瓶颈低频、简单场景兜底

🎯 六、面试高频追问

Redis 锁为什么要用 Lua 解锁、用 uuid 做 value?
防止 A 的锁过期后,B 拿到锁,A 执行完却把 B 的锁 DEL 了。uuid 保证只删自己的;Lua 保证"校验+删除"原子,不被中间插入。
锁过期时间设多少?业务没跑完怎么办?
宁大勿小易死锁,宁小勿大易并发。正解是看门狗自动续期:业务在跑就不断续,掉线就停止续期自然释放。
Redis 和 ZK 锁怎么选?
高并发、能接受极小概率双持 → Redis;金融级强一致、正确性优先 → ZK。也可 Redlock 折中(注意其争议)。

延伸阅读:分布式缓存与三大问题 · ZooKeeper 详解