redlock是一种需手动部署多独立redis主节点的算法,非开箱即用框架;必须显式传入≥3个指向不同物理地址的*redis.client,禁用集群/哨兵客户端,锁有效期为最小ttl减去加锁耗时,需校验isowned并配合业务层二次校验与补偿。

Redlock 不是开箱即用的“框架”,而是一种算法;直接 go get github.com/go-redsync/redsync/v4 后照着文档调用 mutex.Lock(),大概率锁根本没走多数派——你只是在单个 Redis 实例上反复 SETNX。
redsync 初始化必须传多个独立 *redis.Client
很多人写成这样:
client := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
rs := redsync.New(client) // ❌ 错!这还是单点
正确做法是显式构造 ≥3 个指向不同物理地址(或 Docker 网络中隔离的容器)的 *redis.Client,再塞进 redsync.NewPool():
- 每个
*redis.Client的Timeout和ReadTimeout建议设为 ≤50ms,避免某个慢实例拖垮整体判断 - 绝对不要用
redis.NewClusterClient()或redis.NewFailoverClient()—— 它们内部做自动重试/转发,破坏 Redlock 要求的“请求必须发往独立、无状态的主节点”前提 - Redis 实例之间不能共用主从拓扑;3 个实例应是 3 个独立部署的 Redis 主节点(推荐奇数个,如 3 或 5)
Lock() 返回成功后,锁有效期不是你传的 timeout
mutex.Lock("key", redsync.WithExpiry(10*time.Second)) 并不意味着你能安全执行 10 秒。实际剩余有效期 = 最小成功响应的 TTL − 整个加锁过程耗时(T2−T1)。
- 业务逻辑前务必调用
mutex.Expiry()获取真实剩余时间,据此控制最大执行窗口 - 若业务可能超时,需主动调用
mutex.Extend()续期,但每次续期都可能失败,必须检查返回 err 和新Expiry() - 别用
time.Sleep()测超时——GC STW、系统调度延迟、网络抖动都可能吃掉几毫秒,累积就翻车
Unlock() 不是“一定安全”,尤其服务重启或 panic 时
defer mutex.Unlock() 看似稳妥,但 goroutine panic 且没 recover 时,defer 不会执行;服务进程崩溃更不可能触发。
- 锁靠超时自动释放,但期间无任何通知机制,下游只能干等 timeout
- 建议在
Unlock()前加mutex.IsOwned()判断,避免对已过期或不属于自己的锁重复释放(虽 Lua 脚本有校验,但多一层保险) - 真正关键的保护不在锁本身,而在业务逻辑里:查 DB → 加锁 → 再查 DB(防脏读)→ 更新 → 解锁,中间任何一步失败都要回滚或补偿
Redlock 解决的是“多个服务实例争同一 key”的互斥问题,它不保证数据库行一致性,也不替代事务。最常被忽略的一点:锁的粒度和业务语义必须对齐——锁 "order:123" 没法防止另一个服务用 "payment:123" 并发操作同一订单。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











