直接用set命令加锁不安全,因解锁若用del会误删他人锁;必须用setnx加锁+lua脚本校验value后删除,确保加锁与解锁为同一客户端且操作原子。

为什么直接用 SET 命令加锁不安全?
因为 SET key value EX seconds NX 看似原子,但解锁时若用 DEL 会误删别人持有的锁——比如 A 加锁后执行超时,B 获得锁并开始处理,此时 A 恢复并执行 DEL,就把 B 的锁删了。
真正安全的分布式锁必须保证「加锁和解锁都由同一客户端完成」,且解锁操作具备原子性。
redis.Client.SetNX 怎么配合 Lua 实现原子解锁?
Go-redis 不提供开箱即用的分布式锁封装,得自己组合:SetNX 用于加锁(带过期时间防死锁),解锁必须用 Lua 脚本校验 value 是否匹配再删除,否则无法防止误删。
- 加锁用
client.SetNX(ctx, lockKey, randomValue, expiration),randomValue必须全局唯一(推荐用 UUID 或fmt.Sprintf("%s:%d", os.Getenv("HOST"), time.Now().UnixNano())) - 解锁脚本固定写法:
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end - 调用时传入
client.Eval(ctx, script, []string{lockKey}, randomValue),返回值为int64,非 1 表示解锁失败(可能是锁已过期或被别人覆盖)
锁续期(renew)怎么避免业务没做完锁就过期?
如果业务逻辑耗时不确定,又不能把 EX 设得过大(影响故障恢复速度),就得在持有锁期间后台定期续期。Go-redis 本身不提供自动续期,需手动启 goroutine 维护。
- 续期本质是重设 key 的过期时间:
client.Expire(ctx, lockKey, newExpiry),但必须先校验当前 value 是否仍属于自己(否则可能续了别人的锁) - 推荐做法:加锁成功后立即启动一个
time.Ticker,每隔expiration / 3执行一次续期;每次续期前用client.Get(ctx, lockKey).Val()比对 value - 注意:续期操作本身也有失败可能,需设重试上限(如 3 次),失败则主动释放锁并报错
Redlock 算法在 Go-redis 里值得上吗?
单 Redis 实例锁在主从切换时可能失效(写入 master 后未同步到 slave,failover 后新 master 没有该锁),Redlock 通过多节点投票提升可靠性,但实际落地成本高、收益有限。
- Go-redis 官方库不内置 Redlock,第三方包如
github.com/go-redsync/redsync可用,但它要求所有 Redis 实例独立部署、网络稳定,运维复杂度陡增 - 绝大多数业务场景下,用单节点 + 正确的加解锁 + 合理过期时间 + 续期机制,已足够可靠;Redlock 更适合金融级强一致性要求,且你有至少 3 个物理隔离的 Redis 实例
- 如果真要用,注意
redsync.NewPool需传入多个*redis.Client,且每个 client 的Timeout和ReadTimeout必须一致,否则投票结果不可靠
真正难的是锁的生命周期管理——加锁前要不要先检查?超时后是否要回滚已执行的副作用?这些不在 Redis 层解决,得靠业务代码兜底。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











