redis setnx 不能直接当分布式锁用,因其非原子性会导致锁无过期时间而永久卡死;正确做法是用 set 命令的 nx+ex 原子组合,并配合唯一随机 value、业务前缀 key、合理超时、lua 脚本安全释放及库存 lua 原子扣减。

Redis SETNX 不能直接当分布式锁用
直接用 SETNX 加锁再设过期时间,看似两步就完事,但实际会出竞态:命令非原子,锁设成功但过期时间没写入,服务崩了就永远卡死。Go 里用 redis.Client.Set 带 NX 和 EX 参数才是正确姿势,比如:client.Set(ctx, lockKey, randomValue, time.Second*10),它底层走的是 Redis 的 SET key value EX seconds NX 原子命令。
常见错误是自己拼两个命令:client.SetNX + client.Expire,这在压测下 10% 以上请求会漏掉过期时间,导致锁残留。
- value 必须是唯一随机字符串(比如 UUID),否则释放锁时可能误删别人持有的锁
- 锁 key 要带业务标识,比如
lock:goods:1001,别只用lock:1001,避免不同业务冲突 - 超时时间不是越长越好,得略大于业务最大执行时间(比如扣库存+落单耗时 3s,设 5s),否则锁自动释放后别人进来,原流程还在跑,双写就来了
Go 里释放锁必须用 Lua 脚本校验 value
释放锁不是简单调 DEL,否则 A 拿着锁超时了,B 续期成功,A 还按老 value 删除,就把 B 的锁干掉了。正确做法是用 Lua 脚本做原子判断:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
Go 里用 client.Eval 执行这段脚本,KEYS[1] 是锁 key,ARGV[1] 是你生成的随机 value。别手写 if 判断再 del,网络往返 + 条件竞争,稳超卖。
- 不要用
client.Get+if ==+client.Del三步,这是经典反模式 - Lua 脚本里不能用
redis.call("exists", ...)替代get,因为 value 可能已被其他客户端覆盖,必须严格比对 - 如果用 go-redis/v9,直接封装成方法,每次释放都走这个脚本,别图省事绕过去
库存扣减别依赖锁兜底,优先用 Redis Lua 原子脚本
锁只是保底,真正在高并发下扛流量的是 Lua 脚本。把“读库存、判断、扣减”全塞进一个脚本里,由 Redis 单线程串行执行,天然防超卖。例如:
local count = redis.call("get", KEYS[1])
if not count or tonumber(count) <p>Go 里调 <code>client.Eval(ctx, script, []string{stockKey}, nil)</code>,返回值直接判断是否扣成功。比先加锁、再查、再扣、再释放快 3 倍以上,且无锁开销。</p>
- 脚本返回
-1表示库存不足,别忽略返回值直接往下走 - key 名建议统一前缀,比如
stock:goods:1001,方便监控和清理 - 如果要支持批量扣减(比如买 3 件),脚本里改用
DECRBY并加tonumber(count) >= tonumber(ARGV[1])判断
ETCD 分布式锁适合最终一致性校验,不适合高频库存操作
ETCD 锁(如用 clientv3.Txn 实现)延迟高、吞吐低,实测 QPS 不到 Redis 的 1/5。它适合在 Lua 扣完库存后,异步落库前做一次最终校验——比如检查 MySQL 里该商品真实库存是否还够,不够就回滚 Redis 扣减(用 INCR 回填)。别把它放在主链路里挡请求。
seckill.go 主流程里,典型顺序是:Redis Lua 扣库存 → 成功则发 MQ 异步下单 → 失败直接返回 → ETCD 锁只在 MQ 消费端用于防重复落单。
- 别在 HTTP 接口里同步等 ETCD 锁,RT 动辄 50ms+,Gin 协程池会迅速打满
- ETCD 锁的 lease 必须手动续期,否则业务处理慢一点就自动释放,导致多实例同时写 DB
- 如果不用 ETCD,纯 Redis 方案也够用,重点是 Lua 脚本 + 正确的锁释放逻辑,ETCD 属于锦上添花,不是雪中送炭
Lua 脚本的原子性是硬保障,但 value 校验和锁释放的细节,才是 Go 项目里最容易被跳过的坑。很多人写完 SETNX 就以为锁稳了,结果压测一跑,超卖日志里全是“锁已释放但业务还在跑”的痕迹。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











