一、问题背景
在分布式场景中,多个进程/服务器独立运行,访问临界资源时需要互斥访问。分布式锁用于保证同一时刻只有一个进程能访问共享资源。
单机环境: 分布式环境:
+-------------+ +-------------+
| 进程A | | 服务器A |
| 进程B | 临界资源 | 服务器B |
| 进程C | <---------> | 服务器C |
+-------------+ +-------------+
| |
进程锁 分布式锁(Redis)
二、分布式锁的核心要求
2.1 互斥性
锁只能被一个对象持有。 这是分布式锁的基本语义。
2.2 高可用
锁本身也是一个资源,提供锁的服务宕机后,其他服务仍能继续获取锁。
2.3 同一对象操作
获取锁和释放锁必须是同一对象,避免误删他人持有的锁:
问题场景:
1. A 获取锁 lock = uuid_A
2. A 因故超时退出
3. B 获取锁 lock = uuid_B
4. A 恢复后释放锁(DEL lock)<- 错误!释放了 B 的锁
2.4 锁超时
避免持有锁的进程异常退出后锁无法释放,导致死锁。
三、Redis 分布式锁实现
3.1 基础实现:SETNX
lua
-- 获取锁
SETNX lock uuid
-- 释放锁
DEL lock
问题: B 也可以 DEL A 的锁,存在误删风险。
3.2 改进实现:UUID 标识
lua
-- 获取锁
SET lock uuid NX EX 10
-- 释放锁(需判断 UUID)
if redis.call("GET", lock) == uuid then
redis.call("DEL", lock)
return 1
else
return 0
end
关键设计: 用 UUID 标识锁持有者,释放时先判断 UUID 是否匹配。
3.3 Redis 高可用方案
| 模式 | 说明 |
|---|---|
| 哨兵模式 | 主从自动切换,保证高可用 |
| Cluster 模式 | 分片存储,支持大规模集群 |
四、Redis 分布式锁的缺陷
4.1 主从异步复制问题
Redis 主从之间采用异步复制:
主节点:写入 lock=uuid -> 立即返回成功
从节点:同步 lock=uuid -> 可能延迟
问题: 主节点宕机,从节点晋升,但锁数据未同步,导致锁失效。
场景:
1. A 向主节点写入 lock=uuid_A,成功
2. 主节点宕机,锁数据未同步到从节点
3. 从节点晋升为新主节点
4. B 向新主节点写入 lock=uuid_B,成功
5. A 和 B 同时持有锁,互斥性被破坏
这是 Redis 分布式锁最大的缺陷:不是强一致的。
4.2 无法实现公平锁
非公平锁: A 释放锁时,BCD 同时去争抢,先到先得。
时间线:
T1: A 获取锁
T2: A 释放锁
T3: B,C,D 同时争抢 -> 随机选择胜者
公平锁: 按请求顺序排队,FIFO。
Redis 无法实现公平锁,因为:
- 不支持队列排队
- 锁释放时无法主动通知等待者
4.3 锁超时问题
Redis 分布式锁依赖 TTL 超时释放,但存在:
问题1:超时时间难以确定
- 设太长:进程崩溃后锁长时间不释放
- 设太短:业务未执行完就自动释放
问题2:业务执行时间不确定
客户端流程:
1. 获取锁(expire=10s)
2. 执行业务(预计 8s)
3. 业务异常(实际 15s)
4. 锁已自动释放,其他进程获取锁
5. 原进程继续执行,可能破坏数据
五、Redis vs 其他分布式锁方案对比
| 维度 | Redis | MySQL | ZooKeeper | etcd |
|---|---|---|---|---|
| 性能 | 高(内存) | 低(磁盘) | 中 | 中 |
| 高可用 | 支持(主从) | 支持(主从) | 支持 | 支持 |
| 一致性 | 弱(异步复制) | 强(同步复制) | 强(ZAB协议) | 强(Raft协议) |
| 公平锁 | 不支持 | 支持 | 支持 | 支持 |
| 锁粒度控制 | TTL | 行锁/表锁 | 临时节点 | 租约机制 |
| 实现复杂度 | 低 | 低 | 中 | 中 |
六、面试追问 FAQ
| 问题 | 回答要点 |
|---|---|
| Q: Redis 分布式锁为什么不支持公平锁? | Redis 不支持队列机制,锁释放后所有等待者同时争抢 |
| Q: 如何解决 Redis 主从复制导致的锁丢失? | RedLock 算法:向 N 个 Redis 实例获取锁,超过半数成功才算获取成功 |
| Q: 锁超时时间怎么设置? | 根据业务预估执行时间 + 一定余量,或使用看门狗机制续期 |
| Q: 如何防止误删他人锁? | 释放前先 GET 判断 UUID,Lua 脚本保证原子性 |
| Q: RedLock 有什么问题? | 需要向多个 Redis 实例操作,性能下降,且在时钟漂移场景下不可靠 |
七、相关题目
| 题目 | 考察点 |
|---|---|
| Redis 分布式锁如何保证原子性? | Lua 脚本 |
| Redis 分布式锁的超时时间怎么设计? | 业务预估 + 续期机制 |
| 如何实现可重入的分布式锁? | 锁记录中增加持有次数和线程标识 |
| RedLock 算法的原理? | 多节点多数投票 |
八、总结
| 特性 | Redis 分布式锁 |
|---|---|
| 互斥性 | 通过 SETNX 保证 |
| 高可用 | 支持哨兵/Cluster |
| 同一对象操作 | 通过 UUID 标识 |
| 锁超时 | 通过 EXPIRE 实现 |
| 强一致性 | 异步复制可能丢锁 |
| 公平锁 | 不支持 |
| 性能 | 高 |
核心结论: Redis 分布式锁实现简单、性能高,但在主从异步复制场景下存在丢锁风险,不适合对一致性要求极高的场景。对于需要强一致性的场景,建议使用 Zookeeper 或 etcd。
根据零声教育教学写作https://github.com/0voice