集成分布式锁必须用 client.Set 原子命令并显式传 token 和 TTL;释放与续期均须 Lua 脚本校验 token,且续期需绑定 context 防泄漏。

直接在 Echo 的 handler 里调 rdb.SetNX 或包个 sync.Mutex 就算集成分布式锁?不行。多实例部署下,sync.Mutex 完全无效;而 rdb.SetNX 在旧 Redis 或禁 Lua 环境可能 fallback 成两步操作,中间崩溃就导致死锁。
加锁必须用 client.Set 而不是 SetNX
Go-redis/v9 的 client.Set 默认走原子命令 SET key value NX EX seconds,安全可靠;client.SetNX 则可能退化为 SETNX + EXPIRE 两步——若进程 panic、网络中断,key 就永远卡住。
- 显式传
redis.WithValue(token)和redis.WithExpiration(ttl),避免参数错位 -
token必须每次生成新值,推荐uuid.NewString(),别用固定字符串或时间戳 -
EX用秒级足够(如30 * time.Second),误把time.Second当纳秒乘进去会导致锁有效期变成 10⁹ 秒 - TTL 建议设为业务 P99 × 2,比如导出接口最长耗时 1.2s,就设
2 * time.Second;太短易掉锁,太长拖慢故障恢复
释放锁必须走 redis.NewScript + Lua 脚本
用 rdb.Del(ctx, key) 或先 GET 再 DEL 是高危操作:A 拿着锁还没删完,B 已抢到新锁,A 一删就把 B 的锁干掉了——这不是偶发 bug,是必然竞态。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 解锁脚本固定用:
if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end - 定义为常量或用
embed.FS管理,别拼在函数里,方便审计和灰度替换 - 调用时传参顺序必须是
[]string{key}和token,顺序错会导致校验失效 - 返回值必须判断是否为
int64(1),0或error都代表失败,此时应立即中止业务逻辑,而不是重试
长任务必须配看门狗续期,且续期也得用 Lua 校验
锁不能不设 TTL(主从切换后锁丢失),又不能设太长(影响故障恢复),唯一解法是续期——但 A 续了 B 的锁,B 还以为自己持锁在改数据,就完了。
立即学习“go语言免费学习笔记(深入)”;
- 续期脚本必须校验
token匹配才执行EXPIRE,示例:if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("EXPIRE", KEYS[1], ARGV[2]) else return 0 end - 续期间隔建议设为 TTL 的 1/3~1/2(如锁 30s,每 10~15s 续一次)
- 续期 goroutine 必须绑定
context.Context,释放锁时显式调用cancel();否则 goroutine 泄漏会让续期持续运行,最终撑爆连接数 - 别在 handler 里裸启 goroutine,应在锁结构体里保存
ctx和cancel,由锁对象生命周期统一管理
真正容易被忽略的点是:续期脚本和释放脚本的 token 必须来自同一来源,且整个生命周期内不可复用;一旦加锁失败,绝不能启动续期协程——否则它会在后台默默续一个根本不存在的锁。

















