直接用 SET key value NX PX 10000 不够安全,因其仅保证加锁原子性,未解决解锁时误删他人锁的问题;必须用唯一锁值+Lua脚本校验后删除,辅以合理续期和可观测性保障。

为什么直接用 SET key value NX PX 10000 不够安全
单独执行这条命令看似能加锁,但只解决了“加锁原子性”问题,没覆盖解锁场景。最典型的问题是:客户端A加锁成功,执行业务时超时或崩溃,锁自动过期;此时客户端B抢到锁并开始执行;等A恢复后,不加判断就执行 DEL key,结果删掉了B的锁——互斥彻底失效。
根本矛盾在于:解锁操作必须确认“这把锁真是我加的”,不能靠客户端自己记着“我加过锁”这种不可靠状态。
- 锁值必须唯一可识别(比如用随机UUID,而不是固定字符串)
- 解锁必须用 Lua 脚本保证“校验+删除”原子执行
- 锁过期时间要大于业务最大执行时间,但也不能过大(否则故障时阻塞太久)
如何用 Lua 脚本安全解锁
Redis 执行 Lua 是原子的,所以用一个脚本先比对锁值,再删除,能避免误删。这是实际项目中最常复用的解锁逻辑:
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
调用时传入:KEYS[1] 是锁 key,ARGV[1] 是加锁时写入的唯一值(如 "8f4d2a1e-9c3b-4f7d")。返回 1 表示删除成功,0 表示锁已被别人持有或已过期。
- 千万别用两个命令拼:先
GET再DEL——中间可能被其他客户端抢先修改 - 脚本里用
==比较字符串,不是===或其他语法 - 如果用 Jedis,调用
eval()时需显式传入KEYS和ARGV参数列表
锁续期(lease renewal)该不该做?
业务执行时间不确定时,硬设一个长过期时间风险大(节点宕机后锁长期残留),而设短时间又容易提前释放。这时候需要“看门狗”机制自动续期——但仅限于加锁成功的客户端自己续。
- 只有持有锁的客户端才能续期,续期动作本身也要校验锁值(和解锁同理)
- 推荐用独立线程/协程,每隔
expireTime / 3左右执行一次SET key value XX PX newExpire(XX表示仅更新已存在 key) - 如果客户端在续期前崩溃,锁会在原过期时间释放,不会无限延长——这是安全底线
- 注意:Redisson 的
watchdog就是这么实现的,但自己手写要注意心跳失败后的清理逻辑
Redlock 算法真的更安全吗?
单 Redis 实例挂掉会导致锁失效,于是有人提出 Redlock:向 N 个独立 Redis 节点(≥5)同时加锁,多数派成功才算加锁成功。但实际落地中问题不少:
- 时钟漂移会让各节点对“锁是否过期”判断不一致,Martin Kleppmann 等人指出它无法真正保证安全性
- 运维成本陡增:要维护多个独立 Redis 实例,且网络分区时行为难预测
- 绝大多数业务场景下,用单节点 + 正确的加锁/解锁/续期逻辑 + 合理监控,已经足够可靠
- 除非你的系统对锁的强一致性有金融级要求,且能承担 Redlock 带来的复杂度和性能损耗,否则不建议强行套用
真正容易被忽略的是锁的可观测性:加锁失败要不要告警?锁等待时长有没有埋点?续期失败是否记录日志?这些细节比选什么算法更能决定分布式锁在线上稳不稳定。


















