Redis SETNX只是分布式锁起点,必须配合EX过期、唯一value、Lua原子解锁与续期才能满足互斥、防死锁、防误删等要求,单实例+正确实现已覆盖绝大多数场景。

Redis SETNX 命令不是分布式锁的终点,只是起点
直接用 SETNX 加锁看似简单,但会立刻掉进超时失效、客户端崩溃不释放、误删他人锁的坑里。它只解决“没人占着时我能抢到”,不解决“我占着但挂了怎么办”和“我解的是自己的锁吗”。真正可用的分布式锁必须同时满足互斥、防死锁、可重入(可选)、防误删四个条件。
实操建议:
- 永远搭配
EX(过期时间)一起用:SET key value EX 30 NX,避免锁永久残留 - value 必须是唯一标识(如 UUID 或进程+goroutine ID 组合),不能写死成
"1",否则解锁时无法校验归属 - 解锁不能用
DEL key,必须用 Lua 脚本原子比对 value 再删除,否则 A 拿着过期锁还在执行,B 新建了同 key 锁,A 一删就把 B 的锁干掉了 - 不要依赖 Redis 主从同步来保证高可用——主挂了、从升主,旧锁可能在新主上丢失,这是 CAP 下的固有风险,得靠锁续期或业务容忍重入
用 redigo + Lua 实现安全解锁的最小可行代码
Go 生态里 redigo 是最轻量又可控的 Redis 客户端,配合 Lua 脚本能绕过网络往返+判断的竞态。核心就两段:加锁用 SET ... NX EX,解锁用一段固定 Lua 脚本。
常见错误现象:用 GET + DEL 两步操作解锁 → 出现“检查是我,删的时候已不是我”的经典 race condition。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 解锁脚本必须原子执行:
if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end - Go 中调用示例:
redis.Eval(conn, luaScript, []string{lockKey}, lockValue),返回int64类型结果,非 1 表示解锁失败 - 别把 Lua 脚本拼在代码里,用
embed.FS或常量管理,方便审计和复用 - 注意
redigo的连接复用机制,conn.Do()后记得conn.Close()或归还到池,否则锁没发出去就卡住了
锁自动续期(renew)不是锦上添花,而是生产必需
业务逻辑执行时间不确定,而 Redis 锁必须设过期时间防死锁,这两者天然矛盾。不续期 → 锁提前释放 → 其他节点闯入;乱续期 → 把别人刚抢到的锁续上了 → 数据错乱。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
使用场景:长耗时任务(如文件处理、批量导出、第三方 API 调用)必须配续期,且续期动作本身要绑定原锁持有者身份。
实操建议:
- 续期只能由加锁者发起,靠 value 校验,和解锁同理,也得用 Lua:
if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("EXPIRE", KEYS[1], ARGV[2]) else return 0 end - 续期间隔建议设为锁 TTL 的 1/3(比如锁 30s,每 10s 续一次),避开网络抖动导致的误判
- 别用 goroutine 无脑定时续 —— 如果业务 panic 退出,续期 goroutine 可能还在跑,变成“幽灵续期”。应在 defer 或 context.Done() 里显式停掉
- 如果用
go-redsync这类封装库,请确认它是否默认开启 auto-renew;很多库默认关着,得手动mutex.LockWithContext(ctx, &redsync.Options{Expiry: 30 * time.Second, AutoRelease: true})
Redlock 算法在 Go 里基本不用,别被概念带偏
Redis 官方提过的 Redlock(多实例投票)在 Go 实际项目中极少落地。它解决的是单 Redis 实例故障下的锁可靠性,但代价是复杂度飙升、性能下降、且仍无法 100% 避免时钟漂移问题。
性能 / 兼容性影响:每次加锁要串行访问 3–5 个 Redis 实例,P99 延迟翻倍;各实例时间不同步时,可能出现“多数派认为锁有效,少数派认为已过期”的分裂状态。
实操建议:
- 绝大多数业务用单 Redis + 正确实现的 SETNX+Lua+renew 就够了。Redis 实例本身做哨兵或 Cluster,比 Redlock 更务实
- 真有强一致性要求(如金融核心账务),分布式锁不是第一选择 —— 改用数据库唯一约束、乐观锁、或专用协调服务(etcd + lease)更稳
- 如果团队坚持 Redlock,别手写,用
github.com/go-redsync/redsync/v4,但务必压测它的失败路径:某个 Redis 实例超时后,整个锁获取是否降级为单点行为?
真正难的从来不是怎么写一个加锁函数,而是怎么让锁的生命周期和业务执行周期严丝合缝地咬合在一起 —— value 唯一性、续期时机、panic 后清理、context 取消联动,漏掉任意一环,锁就从保险丝变成定时炸弹。

















