
从 SETNX 与 EXPIRE 的故障窗口,到 TTL 超时、误删和主从切换,拆清 Redis 分布式锁面试中最容易被追问的 4 个边界,并给出可直接复述的回答框架。
SET lock:order:123 <唯一随机值> NX PX 30000。DEL。SETNX lock:order:123 client-token-a
EXPIRE lock:order:123 30SETNX 就崩溃,第二条 EXPIRE 没机会执行,这把锁可能永久留在 Redis 里。即使把两条命令放进代码的相邻位置,也没有消除这个故障窗口。SET:SET lock:order:123 client-token-a NX PX 30000NX 表示只有 key 不存在时才设置,PX 30000 表示设置 30 秒过期时间。Redis 的 SET 文档把这两个选项列为同一条命令的条件和过期参数;设置成功返回 OK,条件不满足则不会设置。2client-token-a 不能写成固定字符串。每次加锁都要生成足够唯一的随机值,否则后面释放锁时无法确认「这把锁是不是我拿的」。Redis 官方示例也把随机值作为锁的签名,用来避免客户端误删别人的锁。1老代码里可能会把 SETNX 和 EXPIRE 分开写,但新实现应优先用SET key value NX PX ttl。如果业务必须拆开,就要补偿异常和清理逻辑;这不能改变两步之间存在故障窗口的事实。
PX 30000 拿到锁,但订单处理在第 31 秒才完成。第 30 秒锁已经过期,B 可以重新拿到同一个 key。此时 A 如果还继续写共享资源,互斥关系已经失效。TTL 负责自动释放,续期负责覆盖正常的长任务;两者都不能替代幂等。对扣库存、发放权益这类不能重复执行的操作,我还会在数据库或资源服务侧做条件更新,并考虑 fencing token,让过期客户端的旧请求即使晚到也不能覆盖新请求。
token-a。token-b。DEL lock:order:123。GET 再 DEL 也不够,因为两条命令之间仍可能发生过期和重新加锁。if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
endSET key token NX PX ttl,占锁和 TTL 一次完成;拿不到锁就按业务选择快速失败、退避重试或排队。我会用唯一 token 通过SET key token NX PX ttl获取锁,避免 SETNX 和 EXPIRE 分离造成永久锁。释放时用脚本校验 token 后再删除,防止过期后的旧客户端误删新锁。任务可能超过 TTL 时做有上限的续期,但关键写入仍用幂等和版本校验保护。若依赖主从故障转移,我会说明异步复制可能造成锁丢失,再根据业务是否允许重复执行选择补充 fencing token 或更严格的一致性方案。
SETNX + EXPIRE 的故障窗口讲清,而不只是背命令?NX 是「key 不存在才设置」,PX 是毫秒级过期时间?2DEL?redis.io
This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.