加锁必须用 set 带 nx 和 ex,且 ttl 必须为非零 time.duration;解锁和续期必须用 lua 脚本校验 token;客户端需设 context 超时、封装 lock 结构体、测试前清空 redis。

加锁必须用 SET 带 NX 和 EX,不能只靠 SetNX
GoLand 本身不干预逻辑,但 IDE 的自动补全和类型提示容易让你误以为 rdb.SetNX(ctx, key, value, ttl) 就万事大吉。实际上它底层调用的是 SET key value NX EX seconds —— 这没问题;但如果你漏传 ttl 或传了 0,就会变成无过期时间的死锁,GoLand 不会报错,运行时才暴露。
-
SetNX只保证“key 不存在时才设”,不保证“一定带过期”,必须显式传非零time.Duration - 别用
time.Second * 0或time.Duration(0)当 ttl,Redis 会当成永不过期 - GoLand 调试时看到
result == true就认为加锁成功?错——得检查err == nil且result为true,否则网络超时或 Redis 拒绝连接也会返回false但不 panic - value 必须是唯一字符串,
uuid.NewString()安全;用"lock"或os.Getpid()都可能冲突,GoLand 里 Ctrl+Click 进不去uuid包也不代表它没生效
解锁必须走 Lua 脚本,DEL 或 GET+DEL 在 GoLand 断点里看着对,实际必出问题
你在 GoLand 里单步调试,看到 client.Get(ctx, key).Val() == myToken 为 true,然后执行 client.Del(ctx, key) —— 这段代码在本地单步下永远“正确”。但上线后,两个 goroutine 几乎同时走到这一步,就触发经典 race:A 刚读到自己的 token,B 已续期并覆盖了 key 的 value,A 还是把 B 的锁删了。
- 必须用
redis.NewScript(<code>if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end) - 脚本
Run()返回int64,不是error:等于1才真删了,0表示“不是你的锁”,此时该报错或跳过,不能静默忽略 - GoLand 的 Evaluate Expression 窗口里直接粘 Lua 脚本测不了,得写个最小测试用例跑起来验证
续期不能靠 time.Ticker 盲刷,得先确认自己还是锁持有者
很多人在 GoLand 里新建 goroutine + time.Tick(10 * time.Second),然后无脑调 rdb.Expire(ctx, key, newTTL)。这会导致:A 加锁后崩溃,B 抢到锁,A 的续期 goroutine 还在跑,把 B 的锁又续上了。
GoLand 2026.1.1 是 2026.1 发布后的首个维护修正版本,适合已经开始体验 2026.1 新功能并希望同步补丁的开发者。它更适合用于入门项目、现有项目迁移测试和 IDE 行为验证。
- 续期前必须校验当前 key 的 value 是否仍等于自己的 token,推荐用同一段 Lua 脚本:
if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("EXPIRE", KEYS[1], ARGV[2]) else return 0 end - GoLand 的 Debug Console 里没法模拟 Redis 值被覆盖的场景,得手动
redis-cli改 value 测续期是否失效 - 续期失败(比如 Redis 不可用)不能重试无限次,建议最多 2–3 次,之后应主动
Unlock()并记录 warn 日志,否则 goroutine 悬挂
GoLand 项目结构里最容易漏掉的三件事
不是语法问题,而是工程细节:GoLand 创建新项目时默认不帮你管这些,但线上一出问题就卡在这里。
- Redis 客户端没设
Context超时:&redis.Options{Addr: "localhost:6379", DialTimeout: 5 * time.Second}缺失会导致加锁卡住 30 秒才失败,GoLand Run Configuration 里不显示这个配置项 - 没封装
Lock结构体,而是到处acquireLock()/releaseLock()—— 导致无法绑定续期 goroutine 生命周期,也难做defer unlock()安全兜底 - 测试时用
redis.NewClient(&redis.Options{Addr: "localhost:6379"}),但没启本地 Redis 或没清空 key,导致测试间相互污染;GoLand 的 Test Runner 不自动帮你 flush DB
真正难的不是写对一行 SET 命令,而是让每个环节都经得起并发、崩溃、网络抖动的检验。GoLand 能帮你找 nil pointer,但找不到“续期 goroutine 持有已过期锁”这种逻辑漏洞。










