SET key value NX EX 是分布式锁的底线要求,因其单命令原子性可避免竞态;若分 SETNX+EXPIRE 两步,进程崩溃会导致死锁;必须用该原子命令设锁并自动过期,否则无法保障强一致。

为什么 SET key value NX EX 是分布式锁的底线要求
Redis 的单命令原子性是唯一能避免竞态的根基。用 SETNX + EXPIRE 两步走,中间若进程崩溃,锁就永远卡住——这是最常踩的坑。必须用 SET key value NX EX seconds 一条命令完成“不存在才设值+自动过期”,否则谈不上强一致。
实际写法示例:
SET config:app_timeout "30" NX EX 30
返回 OK 表示抢锁成功;(nil) 表示已被占用。别用 GETSET 或 Lua 脚本绕开这个前提——除非你真需要带校验的释放逻辑(见下一条)。
释放锁时为什么不能只用 DEL key
客户端 A 拿到锁,执行久超时后被自动释放,但 A 还在继续操作,最后用 DEL config:app_timeout 把 B 刚抢到的锁删了——这是典型的锁误删。必须确保“只能删自己设的锁”。
立即学习“go语言免费学习笔记(深入)”;
正确做法是:设锁时 value 用唯一标识(如 UUID 或 clientID+毫秒时间戳),释放时用 Lua 脚本比对再删:
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
调用时传入 KEYS[1] = "config:app_timeout" 和 ARGV[1] = "a1b2c3-4567..."。漏掉这层校验,配置更新就可能被覆盖或中断。
配置更新流程中哪几个点必须加重试和超时
整个流程不是原子的:抢锁 → 读旧值 → 计算新值 → 写入 Redis → 释放锁。任何一步失败都可能导致不一致,尤其网络抖动或 Redis 短暂不可用。
- 抢锁失败时,必须带指数退避重试(如 10ms → 20ms → 40ms),且总超时控制在业务容忍范围内(比如 ≤500ms)
- 写入新配置后,应立即
GET校验是否写入成功;若失败,不能直接释放锁,得先判断锁是否还属于自己 - 释放锁的 Lua 脚本执行返回非 1 时(比如 0 或错误),说明锁已失效或被篡改,此时不应报错中断,而应记录 warn 并继续清理本地状态
Go 里用 redis.Client.Set 和 redis.Client.Eval 时,记得显式设置 Context 的 deadline,否则一次卡住会拖垮整个更新链路。
为什么用 Redlock 在这个场景反而增加风险
Redlock 试图通过多实例投票提升可用性,但它的时钟漂移假设在容器或云环境里极难满足。而字符串配置更新本质是「强一致性优先」操作——宁可短暂不可用,也不能出现两个客户端同时认为自己持有锁。
单 Redis 实例 + 哨兵或 Cluster 主节点写入,配合上面说的原子设锁、带身份校验释放,已经足够覆盖绝大多数配置中心场景。引入 Redlock 不仅没解决根本问题,还会让锁延迟升高、失败路径更复杂,调试成本陡增。
真正该投入精力的地方是:锁粒度是否合理(按配置项 key 锁,而非全局限流)、配置变更是否有幂等写入逻辑、以及下游服务能否容忍短时间旧值缓存。


















