基础锁逻辑易跑通,但线上问题多源于redis/etcd语义边界未对齐:需显式设timeout、合理配连接池、用keepalive防nat断连;etcd lease须独立管理;redis加锁必须原子执行set nx px;value须全局唯一;lua解锁参数顺序与返回值判据须严格正确;ttl应基于压测最慢路径×1.5设定。

直接用 go mod init + go get 拉取客户端库就能跑通基础锁逻辑,但线上出问题几乎都卡在连接复用、超时控制、租约续期或 Lua 脚本参数传递上——不是代码写不对,而是没对齐 Redis/etcd 的语义边界。
go mod 初始化后必须显式配置 timeout 和 dialer
很多人 go get github.com/go-redis/redis/v9 后直接 new Client,结果在高并发下大量 goroutine 卡在 redis.Client.SetNX 上,实际是底层 TCP 连接没设超时。Redis 客户端默认不设 read/write timeout,网络抖动时会 hang 住整个 goroutine。
-
redis.Options中必须显式设置ReadTimeout、WriteTimeout(建议 ≤ 锁 TTL 的 1/3) - 连接池大小别盲目调大:
PoolSize设为 CPU 核数 × 2~4 即可,过大反而触发 Redis 的maxclients限制 - 用
net.Dialer{KeepAlive: 30 * time.Second}配合IdleTimeout,避免 NAT 超时断连后复用脏连接
etcd clientv3.New 要传 context.WithTimeout,且 lease 必须独立管理
etcd 的 clientv3.New 如果不带 context,可能卡在 DNS 解析或初始连接阶段;更关键的是,Grant 创建的 lease 生命周期和 client 实例不绑定,容易出现 lease 到期但 client 还在用老 lease ID 续期失败。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 初始化 client 时用
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second),失败立即 cancel - 每个锁实例应持有自己的
leaseID和LeaseKeepAlivestream,不能多个锁共享一个 lease -
client.Put时必须传clientv3.WithLease(leaseID),漏掉就退化成永久 key,无法自动清理
Redis 加锁必须用 SET key value NX PX ms,不能拆成两步
redis.Client.SetNX 看似简洁,但它只做存在性判断,不设过期时间——一旦进程在 SetNX 成功后崩溃,锁就永远卡死。真实场景必须原子执行「不存在才设值 + 同时设过期」。
- 正确写法:
rdb.Set(ctx, key, value, ttl).Result()(v9 版本中Set默认含 NX+PX 语义) - 错误写法:
rdb.SetNX+rdb.Expire两步,中间崩溃即死锁 - value 必须全局唯一:
uuid.NewString(),禁用时间戳或 PID,避免多实例冲突
Lua 解锁脚本参数顺序错一位就全失效
Redis Lua 脚本里 KEYS[1] 是 key,ARGV[1] 是 value,但 Go 调用时 script.Run(ctx, rdb, []string{key}, clientID) 的参数顺序极易写反——把 clientID 放进 KEYS,或者漏传 []string{key} 导致 KEYS 为空,脚本直接报错 ERR Error running script。
- 解锁脚本固定结构:
if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end - Go 调用必须严格:
script.Run(ctx, rdb, []string{key}, clientID),[]string{key}是 KEYS,clientID是 ARGV - 返回值判据是
result.(int64) == 1,不是布尔值;返回 0 表示锁已不属于当前 client,不是错误
最常被忽略的其实是锁的 TTL 设置——它不是保险丝,而是业务执行时间的预估上限。设太短导致频繁续期甚至脑裂,设太长则故障恢复慢。上线前务必用压测工具模拟最慢路径的实际耗时,再乘以 1.5 倍作为 TTL。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










