结论:生产环境必须用SET key value NX PX加锁、Lua脚本原子释放、看门狗自动续期,否则易因无持有者校验、非原子操作或固定TTL导致误删锁或业务未完成锁已失效。

直接说结论:用 SET key value NX PX 是最简可行的起点,但生产环境必须加锁标识(value 随机唯一)、用 Lua 脚本释放、并配看门狗续期;否则极易误删锁或业务未完锁已过期。
为什么不能只用 SetNX + Del
常见错误是这样写:
rdb.SetNX(ctx, "order:123", 1, 30*time.Second) // ... 执行业务 rdb.Del(ctx, "order:123")
问题有三个:
- 释放锁时没校验持有者 —— 其他协程可能在你业务还没跑完时就删了这个 key
-
SetNX和Expire分两步会破坏原子性(旧版 Redis),即使新版用PX,仍缺持有者标识 - 30 秒固定 TTL,若业务偶发耗时 >30s,锁自动失效,多个节点同时进入临界区
如何安全地加锁与释放
核心是三件事:唯一标识、原子释放、防超时。推荐写法:
立即学习“go语言免费学习笔记(深入)”;
加锁用 client.Set(ctx, key, value, &redis.Options{NX: true, PX: ttl}),其中 value 必须是当前实例/协程生成的随机字符串(如 base64 编码的 16 字节随机数)。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
释放锁必须用 Lua 脚本,确保「判断 value 相等」和「DEL」原子执行:
script := `if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end`
client.Eval(ctx, script, []string{key}, value).Result()
注意:Eval 的 KEYS 和 ARGV 顺序不能错,且脚本里不能用 redis.call("exists") 替代 get 判断,否则无法防止 ABA 问题。
为什么需要看门狗(Lock Refresh)
业务耗时不可控,硬设 TTL 不现实。看门狗本质是后台 goroutine 定期执行 PTTL 检查剩余时间,若低于阈值(如 10s),就调用 Lua 续期:
script := `if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("pexpire", KEYS[1], ARGV[2]) else return 0 end`
client.Eval(ctx, script, []string{key}, value, newTTL.Milliseconds()).Result()
关键点:
- 续期必须检查 value 是否仍是自己,否则可能把别人刚抢到的锁又续上了
- 续期间隔建议设为 TTL 的 1/3(如 TTL=30s,则每 10s 续一次),避免频繁请求
- goroutine 生命周期需绑定到锁持有上下文,业务结束时要 cancel
redsync 或 Redisson 是不是更省事
是,但代价明确:
-
redsync基于 Redlock 算法,需连接 ≥3 个独立 Redis 实例,网络开销翻倍,部署复杂度上升;单节点场景纯属过度设计 - Redisson 是 Java 生态产物,Go 中无官方对应实现;社区移植版(如 goredis)往往缺失看门狗自动管理或 Lua 脚本兼容性
- 自研轻量锁(
TryLock/Unlock/Refresh三接口)在 90% 场景下更可控、更易 debug
真正容易被忽略的是锁的 context 传递 —— ctx 要带 timeout 或 deadline,否则网络卡住时 Set 或 Eval 会无限阻塞,整个 goroutine 就挂死了。

















