go语言redis分布式锁必须用set key value nx px原子命令加唯一token,并通过lua脚本校验value后原子删除,否则易因非原子操作导致锁误删或死锁。

go-redis 实现的 Redis 分布式锁,不是“开箱即用”的互斥方案,而是需要你手动保障原子性、所有权校验和超时一致性。直接调用 SetNX 或 Del 很容易踩坑,导致锁失效或误删。
为什么不能只用 SetNX + Del
这是最常见也最危险的写法:加锁用 SetNX,解锁直接 Del。问题在于——
- 锁过期后被自动删除,但客户端仍以为自己持有锁,继续执行业务逻辑
-
Del没有校验所有权,A 的锁过期被 B 获取,A 后续调用Del会把 B 的锁删掉 - 网络延迟或 GC 暂停可能导致业务耗时超过 TTL,锁提前释放而业务未结束
所以必须用 Lua 脚本保证「判断值一致 + 删除」是原子操作。
Unlock 必须用 Lua 脚本校验 value
解锁逻辑本质只有一行 Lua:
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end
对应 Go 实现必须传入唯一标识(如 uuid.NewString()),且该值在 TryLock 和 Unlock 中严格一致:
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
-
SetNX的value参数必须是客户端私有、不可预测的字符串,不能是固定值(如"1") -
Eval调用时,KEYS[1]是锁 key,ARGV[1]是加锁时写入的 value - 返回结果需判断是否为
int64(1),否则说明解锁失败(可能是锁已过期或被他人覆盖)
TTL 设置要大于业务最长执行时间
锁的过期时间不是“越长越安全”,而是必须满足:TTL > 最坏情况下的业务耗时 + 网络抖动 + 客户端 GC 延迟。实践中建议:
- 先压测出业务 P999 耗时,再加至少 2–3 秒缓冲
- 避免设成固定 30s —— 如果某次请求卡在 IO 上 40s,锁已失效,但业务还在跑
- 不要依赖续约(renew)机制,除非你实现了带心跳的 Lease 模型;简单场景下,宁可失败重试,也不要盲目续租
阻塞式获取锁要控制重试间隔与退出条件
TryLock 返回 false 并不意味着永远拿不到锁。若需阻塞等待,必须带退避策略:
- 用
time.AfterFunc或time.Ticker控制轮询间隔,初始 10ms,指数退避至最大 500ms - 设置总超时(如 5s),防止无限等待——微服务调用链中,上游通常已有整体超时控制
- 每次重试前检查 context 是否已取消(
ctx.Err() != nil),及时退出 goroutine - 不要在循环里无条件
time.Sleep,否则无法响应 cancel
真正容易被忽略的,是 value 的生成方式和 TTL 的动态校准。UUID 能解决冲突,但无法应对业务耗时突增;TTL 写死在代码里,等于把锁的安全性交给预估精度。生产环境建议把 TTL 作为参数传入,并随业务指标动态调整。










