Valkey多节点分布式锁必须用Redlock算法而非原生SET,因其能防止单点故障和脑裂;需部署≥5个独立实例、禁用持久化、设合理TTL,并严格保证加锁与解锁使用同一client实例。

Valkey(原Redis)在Golang中做分布式锁,单节点用 SET key value NX PX 就够了;但多节点协调必须防止单点故障和脑裂——直接上 Valkey Cluster 或 Sentinel 模式时,SET 命令不保证原子性跨槽/跨实例,所以不能靠客户端拼逻辑硬搞。
为什么不能直接用 redigo/redis-go 的 SET + EXPIRE
Valkey Cluster 中 key 被哈希到不同 slot,而 SET key val NX PX 10000 是原子的,但 GET + DEL 解锁不是:如果锁在 node A,客户端连的是 node B,GET 会重定向,DEL 可能发到错误节点;更糟的是,集群 failover 过程中 slot 迁移未完成时,命令可能被拒绝或返回 MOVED/ASK,导致锁状态不可知。
- Valkey Cluster 不支持
EVAL脚本跨 slot 执行,Lua 脚本里不能同时操作多个 key(哪怕只是校验+删除) -
redis-go默认 client 对MOVED做自动重试,但重试窗口内锁可能已被其他客户端续期或释放,造成误删 - 用 Sentinel 模式时,主从切换期间可能出现“双主”,两个客户端各自拿到锁
用 redlock-go 实现多节点容错锁(非 Cluster 模式)
Redlock 算法要求部署 ≥5 个独立 Valkey 实例(不共用进程、不共享网络路径),客户端向多数派(≥3)成功获取锁才算生效。它不依赖集群拓扑,适合跨机房或混合部署场景。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 必须用
redlock-gov4+,它默认禁用WATCH/MULTI,只走SET key val NX PX+ 随机 token,避免 Lua 脚本兼容性问题 - 每个 Valkey 实例必须关闭持久化(
save "")和 AOF,否则 fork 阻塞会导致锁超时漂移 - 初始化时传入的
endpoints是纯地址列表,如[]string{"10.0.1.1:6379", "10.0.1.2:6379", ...},不要带redis://前缀 - 锁有效期建议设为操作耗时的 3–5 倍,比如 DB 写入通常 200ms,就设
ttl = 1000(毫秒)
dl, err := redlock.New(
[]string{"10.0.1.1:6379", "10.0.1.2:6379", "10.0.1.3:6379"},
redlock.SetTTL(1000),
redlock.SetRetryTimes(3),
redlock.SetRetryDelay(100),
)
if err != nil {
log.Fatal(err)
}
lock, err := dl.Lock("db:order:12345", 1000)
解锁必须用同一个 redlock.Client 实例调用 Unlock
Valkey 锁的本质是“谁加的锁谁解”,token 是随机生成并存在 lock 结构体里的。如果换 client 或重建 lock 对象,Unlock() 会因 token 不匹配静默失败——看起来像没解锁,其实锁还挂着。
立即学习“go语言免费学习笔记(深入)”;
- 不能把
lock存到 context 或全局 map 里跨 goroutine 复用;必须在加锁 goroutine 内完成Unlock(),或用defer lock.Unlock() - 若业务逻辑 panic,需用
recover()捕获后手动Unlock(),否则锁会残留到 TTL 过期 - Valkey 实例宕机时,
Unlock()可能返回redlock.ErrNotLocked或redis.Nil,可忽略;但若返回redlock.ErrFailed(多数节点失败),说明锁状态已不确定,应触发告警而非重试
Redlock 不是银弹:它假设各 Valkey 实例时钟漂移 ≤ 预期误差的 1/2,且网络分区时间短于 TTL。生产环境若无法满足,就得切到基于 Raft 的协调服务(如 etcd),或者接受最终一致性,用数据库唯一索引+重试代替强锁。

















