加锁必须原子执行且 value 全局唯一、ttl 合理,解锁需 lua 脚本校验 value,长任务须自动续期,单 redis 实例多数场景足够,redlock 仅适用于强一致性且多物理节点场景。

直接用 SET key value NX EX 加锁,不配自动续期、不校验 value 就解锁,线上扛不住 5 分钟——不是性能问题,是逻辑漏洞。
加锁必须原子执行:SetNX 不能单独用
常见错误现象:用 Exists + Set + Expire 三步模拟加锁,结果在第二步失败或被中断时留下无过期时间的死锁。
-
SetNX(ctx, key, value, ttl)内部封装的就是SET key value NX EX seconds,一步到位,缺一不可 -
value必须是全局唯一标识,推荐uuid.NewString(),严禁硬编码为"1"或复用进程 PID -
ttl要略大于业务最长耗时,但别设成time.Hour——超长 TTL 会让故障恢复变慢,5–30 秒更合理 - 如果用
redigo,得手写Do("SET", key, value, "NX", "EX", int(ttl.Seconds())),注意参数顺序和类型
解锁必须用 Lua 脚本:DEL 是危险操作
直接调 rdb.Del(ctx, key) 是最常见误删根源:A 拿锁慢,超时释放;B 抢到锁;A 结束仍执行 Del,就把 B 的锁干掉了。
- 标准解锁脚本只有 4 行:
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end - Go 中调用必须传两个参数:
[]string{key}和clientID(即加锁时生成的value) - 返回
int64(0)表示非持有者,此时应记录 warn 日志,而不是 panic 或重试 - 用
redis.NewScript()封装,避免每次传输字符串;若用UniversalClient,建议预加载脚本
长任务必须自动续期:keepAlive 不是可选项
业务耗时不确定(如调第三方 API、批量写库)时,不续期 = 锁提前失效 = 多个节点闯入临界区。
- 续期频率建议设为
ttl / 3(例如ttl=15s,则每5s检查一次) - 续期本身也要原子:先
GET校验value是否匹配,再PEXPIRE,否则可能把别人刚抢到的锁续上 - 用独立 goroutine 执行,绑定
context.WithCancel,主流程结束时调cancel()防泄漏 - 续期 Lua 脚本返回
0,说明锁已丢失,必须立刻中止后续业务逻辑,避免污染数据
单 Redis 实例够不够?Redlock 什么时候该上
多数业务用单实例 + 正确实现已足够,Redlock 不是银弹,它解决的是主从切换导致的锁凭空消失问题,但代价是复杂度和延迟上升。
- 金融、订单等强一致性场景,且已有 ≥3 个物理隔离的 Redis 节点,才考虑
redlock-go - Redlock 要求“大多数节点加锁成功 + 总耗时
- 主从异步复制下,单实例锁本身就有 AP 属性,续期机制比多节点协调更能缓解风险
- 别为了“高可用”强行上 Redlock——90% 的超卖、重复下单问题,都出在 value 不唯一或没续期
真正难的不是写对那几行 Lua,而是让每个服务实例都严格遵守 value 唯一性、续期绑定生命周期、解锁前校验这三条铁律。漏掉任意一条,分布式锁就退化成装饰性代码。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











