好的,阿杰,我帮你整理一份 Redis 实现分布式锁的完整指南,包括 7 种常见方案、原理分析、优缺点,以及 正确使用姿势。这对于高并发系统或者分布式应用非常关键。
Redis 分布式锁指南
1️⃣ 分布式锁概念
- 分布式锁:用于在分布式环境中保证同一资源的 互斥访问
- 核心要求:
- 互斥性:同一时刻只有一个客户端能持有锁
- 可重入性(可选):同一客户端可以重复获取
- 超时释放:防止死锁
- 可靠性:锁状态不会丢失
- Redis 优势:
- 单线程保证操作原子性
- 支持过期时间,避免死锁
- 可通过
SET key value NX PX expire
实现
2️⃣ 7 种 Redis 分布式锁实现方案
方案 1:SETNX + EXPIRE
SETNX lock_key 1
EXPIRE lock_key 10
- 原理:
- 使用
SETNX
(key 不存在才设置)加锁 - 使用
EXPIRE
设置超时时间
- 使用
- 缺陷:
- 非原子操作:在
SETNX
和EXPIRE
之间宕机会导致死锁
- 非原子操作:在
- 不推荐
方案 2:SETNX + Lua 脚本删除
- 思路:
- 给锁加唯一标识(UUID)
- 释放锁前检查是否是自己持有
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
- 优点:防止误删其他客户端锁
- 缺点:依然需要手动处理过期时间
方案 3:SET key value NX PX expire(一条命令)
SET lock_key UUID NX PX 10000
- 解释:
NX
:key 不存在才设置PX
:过期时间(毫秒)
- 优点:
- 原子性操作,解决方案 1 的死锁问题
- 缺点:
- 锁超时可能导致业务执行未完成就释放锁
方案 4:Redisson Java 客户端
- 特性:
- 高级客户端,封装了分布式锁实现
- 支持可重入锁、看门狗机制自动延长过期时间
RLock lock = redisson.getLock("myLock");
lock.lock(10, TimeUnit.SECONDS);
try {
// 业务逻辑
} finally {
lock.unlock();
}
- 优点:
- 简单、安全、可重入
- 支持自动续期
方案 5:Redlock(多 Redis 实例)
- 原理:
- 同时在 N 个 Redis 节点尝试加锁
- 只要获得多数节点锁,则认为加锁成功
- 超时释放防止死锁
- 优点:适用于分布式高可靠环境
- 缺点:复杂,需要多节点部署
- 参考:Redlock 官方算法
方案 6:队列 + Lua 脚本(公平锁)
- 思路:
- 用 List 保存请求队列
- Lua 脚本保证原子性
- 按顺序加锁,保证公平性
- 适用场景:
- 对请求顺序敏感的业务
方案 7:Pub/Sub + 锁释放通知
- 原理:
- 客户端获取锁失败时,订阅释放通知
- 锁释放后立即尝试获取
- 优点:
- 避免频繁轮询 Redis(减少 CPU 消耗)
- 缺点:
- Pub/Sub 可靠性不如轮询或 Redlock
3️⃣ 正确使用 Redis 分布式锁的姿势
- 加锁使用唯一标识
- 每个客户端持有不同 UUID,释放时检查防止误删
- 设置合理超时时间
- 锁超时 > 业务执行时间
- Redisson 可使用 看门狗机制自动续期
- 业务执行放在 try-finally
- 避免异常导致锁未释放
- 高可用场景选择 Redlock 或 Redisson
- 单节点 Redis 在宕机时可能导致锁不可靠
- 避免死锁
- 使用超时机制
- 不在锁内调用耗时操作或阻塞操作
4️⃣ 总结对比
方案 | 原子性 | 可重入 | 自动续期 | 适用场景 |
---|---|---|---|---|
SETNX+EXPIRE | ❌ | ❌ | ❌ | 简单单节点,不推荐 |
SETNX+Lua | ✅ | ❌ | ❌ | 单节点安全加锁 |
SET NX PX | ✅ | ❌ | ❌ | 单节点,简单安全 |
Redisson | ✅ | ✅ | ✅ | Java 分布式系统首选 |
Redlock | ✅(多数节点) | ❌ | ❌ | 高可靠分布式系统 |
Lua队列锁 | ✅ | ❌ | ❌ | 公平锁场景 |
Pub/Sub通知 | ✅ | ❌ | ❌ | 避免轮询、节省资源 |
💡 核心结论:
- Java 开发 → 推荐 Redisson
- 高可用、多节点 → 推荐 Redlock
- 单节点、简单业务 → 使用
SET key value NX PX expire
即可 - 必须防止误删 → 加 UUID + Lua 脚本
发表回复