redis setnx不能直接当分布式锁用,因其无法原子性设置key和过期时间,易导致死锁;应使用set key value ex seconds nx命令,并配合唯一value、lua校验解锁、连接复用控制等机制保障可靠性。

Redis SETNX 命令为什么不能直接当分布式锁用
因为 SETNX 只能保证“设置不存在的 key”,但无法原子性地同时设置过期时间,导致锁可能永远不释放。如果进程在 SETNX 成功后、执行 EXPIRE 前崩溃,这个锁就卡死了。
真实场景下,高并发抢锁时,你得确保「加锁 + 设过期」是一次原子操作。Redis 2.6.12 之后推荐用 SET key value EX seconds NX,它把三个动作压进一条命令里。
-
NX表示仅当 key 不存在才设置,避免覆盖别人持有的锁 -
EX(秒)或PX(毫秒)必须显式指定,否则没有自动释放机制 - value 必须是唯一标识(比如 UUID 或
os.Getpid()+ 时间戳),后续解锁时靠它校验所有权
Go 中用 redigo 实现带校验的解锁逻辑
很多人用 DEL key 直接删锁,这是错的——可能删掉别人刚续上的锁。正确做法是 Lua 脚本比对 value 再删,保证“谁设的谁删”。
redigo 自带 Do 支持 Lua 执行,下面这段是核心解锁脚本:
立即学习“go语言免费学习笔记(深入)”;
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
调用时传入 key 和自己的 value:
redis.Do("EVAL", script, 1, "my_lock_key", "b7e5a2f9-1c3d-4a8e-9f0b-2d1e4c6a8f01")- 返回
int64(1)表示删除成功,0表示锁不属于你 - 别用
Get+Del两步走,中间可能被其他协程抢先修改
锁自动续期(renew)该不该做?什么情况下必须做
必须做:当业务逻辑执行时间不确定(比如调外部 HTTP 接口、批量写 DB),且明显可能超过锁的 TTL 时。否则锁提前过期,别的 goroutine 就会误入临界区。
但续期不是无脑延长——得确保还是自己持有的锁,且不能无限续。常见做法是启动一个单独的 goroutine,在锁剩余时间约 1/3 时尝试用相同 value 续期:
- 用
SETEX或PEXPIRE更新 TTL,但前提是先用 Lua 校验 value 是否匹配 - 续期失败(返回 0)说明锁已丢失,应立刻中止当前业务流程,避免脏写
- 别让续期 goroutine 活过主业务结束,记得用
context.WithCancel控制生命周期
redigo 连接池配置不当引发的锁失效问题
现象是:本地测试正常,上线后偶尔出现“锁没生效”或“同一时刻多个实例进入临界区”。大概率是连接池复用了底层 TCP 连接,而 Redis 在 pipeline 或事务中对响应顺序有强依赖。
关键点:
- 确保每个锁操作(加锁、续期、解锁)都使用同一个
redis.Conn,不要从池里取一次、用完放回、再取一次 - redigo 的
Pool.Get()返回的是可重用连接,但你得自己管理它的生命周期:加锁后 hold 住 conn,直到解锁完成 - 连接空闲超时(
IdleTimeout)建议设为略大于锁 TTL,防止 conn 被池回收时锁还在用 - 别用
redis.Dial每次新建连接——开销大,且容易触发 Redis 的maxclients限制
锁最难的从来不是怎么写,而是怎么证明它在超时、网络分区、进程崩溃这些边界条件下依然可靠。value 唯一性、Lua 校验、conn 生命周期这三处,漏掉任何一环,高并发下都可能出事。










