redis分布式锁不能直接用del解锁,因缺乏原子性校验易误删他人锁;必须用lua脚本比对value后删除,且加锁、续期、解锁均需绑定同一唯一token。

Redis分布式锁为什么不能直接用 SET key value EX seconds
因为这只能保证“加锁”原子性,无法保证“解锁”安全。常见错误是客户端A加锁成功,但执行超时被踢出,此时A仍用旧key调用 DEL,可能误删B刚设的锁——这是典型的锁误释放问题。
真正可用的方案必须满足:加锁、续期、解锁三者都绑定同一客户端身份,且解锁操作具备原子性。
- 加锁用
SET key value NX EX seconds(NX保证仅当key不存在才设置) - value 必须是全局唯一标识(如UUID或进程+goroutine ID组合),不能是固定字符串
- 解锁必须用 Lua 脚本:
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end - 续期(renew)需在锁快过期前用同样value重设过期时间,且只对当前持有者生效
Go里用 redigo 实现带自动续期的锁
redigo 是最常用的 Redis Go 客户端,但原生不提供分布式锁封装,得自己补全逻辑。关键点不是“怎么连Redis”,而是“怎么让锁不因网络抖动或GC停顿而意外失效”。
- 锁获取后立即启动一个
time.Ticker协程,每隔expire/3秒尝试续期(避免太频繁,也防止续期延迟导致过期) - 续期命令仍用
EVALLua 脚本,校验 value 匹配才更新 TTL - 锁结构体要包含
conn、key、token(即 value)、cancel(用于停止续期) - 务必在 defer 中调用
Unlock(),且Unlock()内部要检查 token 是否为空——防止重复 unlock 或 panic
示例片段:
func (l *RedisLock) Unlock() error {
if l.token == "" {
return errors.New("lock not held")
}
script := "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"
n, err := redis.Int64(l.conn.Do("EVAL", script, 1, l.key, l.token))
if err != nil {
return err
}
if n == 0 {
return errors.New("unlock failed: token mismatch or key expired")
}
return nil
}
Redsync 库能直接用吗?哪些地方要改
Redsync 封装了基于 Redis 的 multi-node Redlock 算法,但默认行为对多数内部服务过于保守:它要求多数节点(N/2+1)加锁成功才算获得锁,引入额外延迟和复杂度。
- 如果你用的是单 Redis 实例(含主从),直接关掉 Redlock 模式,用
rs.NewSafeClient+ 单 client,否则性能差且无实际收益 - 默认重试间隔是 100ms,高并发场景下建议调小(如 10ms),但别设成 0——会打爆 Redis
- 它不自动续期,必须自己起 goroutine 调用
Extend(),且Extend()不校验持有者,需确认传入的 token 和加锁时一致 - 注意
Extend()返回新过期时间,若返回 0 表示续期失败(比如 key 已被删),应主动放弃操作
锁粒度选 key 还是 hash field?
别一上来就用 HSET resource:123 lock_token "abc"。Hash 结构看似能复用 key,但 HDEL 无法原子校验 field 值,Lua 脚本也更难写;而单 key 方案所有操作都围绕一个字符串展开,边界清晰。
- 业务上需要区分资源粒度时(如订单ID、用户ID),就把它们拼进 key 名,例如
lock:order:789,而不是用 Hash 存多个锁 - 如果真要批量加锁(如同时锁 5 个订单),不要用一个锁包住全部——应并行获取多个独立锁,失败则回滚已获锁
- 避免长 key:Redis key 长度影响内存和网络开销,超过 1KB 的 key 建议哈希化(如
lock:order:sha256(id))
真正的难点不在实现,而在锁生命周期管理:谁负责续期、超时怎么判定、panic 后是否清理、跨 service 调用时锁上下文如何传递——这些没法靠库自动解决。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











