不能只用SETNX+EXPIRE,因存在三大断裂点:加锁成功后EXPIRE未执行导致死锁、锁过期后业务仍在运行引发并发、DEL解锁未校验归属造成误删;必须用Lua脚本原子化实现。

分布式锁不是“加个锁就完事”的东西,它是在多台机器、多个进程同时争抢同一资源时,必须由外部统一协调的互斥机制。Golang 微服务里用 Redis 实现分布式锁,核心就两条:SETNX 保证加锁原子性,EXPIRE 防死锁——但直接拼这两个命令会出问题,比如加锁成功却没设上过期时间,或解锁时删了别人持有的锁。
为什么不能只用 SETNX + EXPIRE?
看似简单,实际有三个致命断裂点:
-
SETNX成功后,网络抖动导致EXPIRE没发出去 → 锁永远不释放 - 业务执行超时,锁自动过期,但代码还在跑 → 其他协程拿到锁,同时操作共享资源
- 解锁用
DEL,但没校验锁归属 → A 拿着锁干活,B 直接DEL把 A 的锁干掉
所以必须把“判断是否存在 + 设置值 + 设过期时间”三步压进一个原子操作,Redis 的 EVAL 脚本是唯一靠谱解法。
redsync 是怎么绕过这些坑的?
它封装了 Lua 脚本 + 随机 token + 过期时间校验,关键行为如下:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- 加锁时写入的 value 是随机字符串(如
uuid),不是固定值 - 解锁时先
GET判断 value 是否匹配,再DEL,避免误删 - 默认锁过期时间 8 秒,但可传参覆盖,例如
rs.NewMutex("order:123", redsync.WithExpiry(30*time.Second)) - 支持重试机制:默认最多尝试 32 次,每次间隔 50ms,避免无限轮询
Redlock 算法真有必要吗?
单 Redis 实例在主从切换时可能丢锁,Redlock 通过向 ≥3 个独立 Redis 节点请求锁来缓解这个问题,但它不是银弹:
- 要求节点数为奇数(至少 3 个),且网络延迟远小于锁过期时间
- 加锁耗时必须 ≤ 锁总有效期的 1/2,否则即使多数节点成功也视为失败
-
redlock-go库能帮你做多数派判断,但无法解决时钟漂移——这是 CAP 下的固有限制 - 普通库存扣减、幂等接口这类场景,单节点
redsync完全够用;金融级资金流水才值得上 Redlock
真正容易被忽略的是锁续期问题:业务逻辑执行时间不确定时,光靠初始过期时间撑不住,得配看门狗(watchdog)定期刷新 TTL——这在 redsync 里要自己实现,redisson 才原生支持。别等到线上出现重复下单才想起来查锁是不是提前过期了。

















