题目
设计高并发场景下的Redis分布式锁,解决锁续期与锁释放问题
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
分布式锁原理,Redis原子操作,锁续期(Watchdog),锁释放的安全性,高并发场景设计
快速回答
实现安全可靠的Redis分布式锁需要解决三个核心问题:
- 原子加锁:使用SET命令的NX和PX选项确保原子性
- 锁续期:通过后台线程(Watchdog)定期延长锁过期时间
- 安全释放:使用Lua脚本验证锁归属后再释放
典型实现方案:Redisson的RLock,包含锁续期机制和释放保护。
解析
1. 核心问题与解决思路
分布式锁三大挑战:
- 原子性获取锁:避免多个客户端同时获得锁
- 锁续期(Watchdog):防止业务执行超时导致锁自动释放
- 安全释放:确保只有锁持有者能释放锁
2. 基础实现方案
原子加锁(Redis命令):
SET lock_key $unique_id NX PX 30000NX:仅当key不存在时设置PX 30000:设置30秒过期时间$unique_id:客户端唯一标识(如UUID)
安全释放锁(Lua脚本):
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end3. 锁续期(Watchdog)设计
必要性:业务执行时间可能超过锁的初始过期时间
实现方案:
- 获取锁成功后启动守护线程
- 每10秒(建议为过期时间的1/3)检查锁是否仍持有
- 通过Lua脚本延长锁过期时间:
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("pexpire", KEYS[1], ARGV[2]) else return 0 end - 业务完成时停止续期并释放锁
4. 完整流程示例(Java伪代码)
// 加锁
String lockKey = "order_lock";
String clientId = UUID.randomUUID().toString();
boolean locked = redis.set(lockKey, clientId, "NX", "PX", 30000);
if (locked) {
// 启动Watchdog线程
Thread watchdog = new Thread(() -> {
while (!Thread.interrupted()) {
// 每10秒续期一次
redis.eval(续期Lua脚本, lockKey, clientId, 30000);
sleep(10000);
}
});
watchdog.start();
try {
// 执行业务逻辑
processBusiness();
} finally {
// 停止续期并释放锁
watchdog.interrupt();
redis.eval(释放锁Lua脚本, lockKey, clientId);
}
}5. 最佳实践与注意事项
- 唯一客户端标识:必须使用全局唯一ID(如UUID+线程ID)
- 过期时间设置:初始时间需大于业务平均处理时间,建议设置超时阈值
- 网络分区处理:Redis集群故障时可能产生脑裂,需配合Redlock等算法
- 避免GC停顿:JVM Full GC可能导致Watchdog线程暂停,需监控GC状态
- 推荐方案:优先使用成熟库(如Redisson的RLock)
6. 常见错误
- 错误1:使用SETNX+EXPIRE非原子操作(可能死锁)
- 错误2:未验证锁归属直接删除(可能释放他人锁)
- 错误3:未处理锁续期导致业务中途锁失效
- 错误4:未设置超时时间(服务崩溃时永久死锁)
7. 扩展知识
- Redlock算法:多Redis实例的分布式锁协议,解决单点故障问题
- 锁等待队列:通过Redis的List或ZSET实现获取锁排队
- 锁重入:同一线程多次加锁需维护计数器(Redisson已实现)
- 异步复制风险:主从切换可能导致锁丢失,需开启WAIT命令