sync.Mutex不能用于分布式锁,因其仅限单进程内存有效,跨进程、跨机器时互不感知;真正可用的分布式锁须依赖Redis、Etcd等共享存储,满足互斥性、超时防死锁、加锁者释放三大条件。

为什么不能直接用 sync.Mutex 做分布式锁
因为 sync.Mutex 只在单进程内有效,跨机器、跨进程完全不生效。你起 10 个服务实例,每个都拿到自己的 sync.Mutex,根本互斥不了——锁是假的,数据竞争照旧。
真正的分布式锁必须依赖一个所有节点都能访问的共享存储,比如 Redis、Etcd 或 ZooKeeper。目前最常用、最轻量的是基于 Redis 的实现,核心靠 SET key value NX PX timeout 命令保证原子性。
常见错误现象:
– 手写 GET + SET 判断再设值:竞态明显,两个请求同时读到空,都写入成功
– 忘记设置过期时间(PX):节点崩溃后锁永远不释放,服务卡死
– 用固定字符串当 value:无法安全解锁(别人也能删你的锁)
Redis 实现的关键三步:加锁、续期、解锁
加锁必须用原子命令,value 要唯一(推荐用随机 UUID),并显式设过期时间;解锁必须校验 value 再删,否则可能误删他人锁;长任务还得定期续期,避免锁提前过期。
- 加锁:用
redis.Client.SetNX(ctx, key, value, ttl),返回true表示抢锁成功 - 解锁:不能直接
DEL,要用 Lua 脚本保证“判断 value 相等再删”,例如:if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end - 续期:另起 goroutine 调用
redis.Client.Expire(ctx, key, newTTL),但仅当 value 仍匹配时才续(需先 GET 校验,或用更稳妥的 Lua 脚本)
用 github.com/go-redsync/redsync/v4 省掉手写坑
自己拼 Lua、管续期、处理网络超时、重试逻辑,很容易漏掉边界情况。redsync 封装了基于 Redis 的多数派共识(Redlock 算法),自动处理节点故障、时钟漂移、网络分区等问题,比单点 Redis 更健壮。
使用场景:需要强一致性保障的支付扣款、库存扣减等关键路径。
注意点:
– 它默认连 3 个独立 Redis 实例(或哨兵/集群中的不同节点),少于半数成功即判失败
– mutex.Lock() 会阻塞直到获取成功或超时,超时时间由 redsync.NewMutex(..., redsync.WithExpiry(30*time.Second)) 控制
– 解锁必须调用 mutex.Unlock(),它内部做了 value 校验和 Lua 执行
简短示例:
pool := redsync.NewPool(client1, client2, client3)
rs := redsync.New(pool)
mutex := rs.NewMutex("order:12345", redsync.WithExpiry(10*time.Second))
if err := mutex.Lock(); err != nil {
// 失败,可能重试或降级
}
defer mutex.Unlock() // 注意:不是 defer func() { mutex.Unlock() }()
Etcd 版本更适配 Kubernetes 场景
如果你的服务跑在 K8s 里,且集群已部署 Etcd,用 go.etcd.io/etcd/client/v3 实现更自然——天然支持租约(Lease)、监听、多版本并发控制(MVCC),没有 Redis 的单点脑裂风险。
核心操作:
– 加锁:先 client.Grant(ctx, 15) 创建租约,再 client.Put(ctx, key, value, clientv3.WithLease(leaseID))
– 解锁:直接 client.Delete(ctx, key),租约到期自动清理
– 续期:调用 client.KeepAlive(ctx, leaseID) 返回 channel,持续收心跳响应即可
容易踩的坑:
– 忘记检查 Put 返回的 header.Revision,导致多个客户端写入同一 key 成功(Etcd 允许覆盖,得靠业务层用 WithPrevKV + 比较 revision 实现“仅当不存在时写入”)
– KeepAlive channel 关闭后没重连,导致租约静默过期
所以生产中建议直接用封装好的 github.com/sony/gobreaker 风格的库,比如 go.etcd.io/etcd/client/v3/concurrency 包里的 mutex ——它已内置 revision 检查和自动续期逻辑。
真正难的从来不是“怎么写个锁”,而是“怎么让锁在节点宕机、网络延迟、时钟跳变、GC STW 时依然不丢不重不误放”。这些细节藏在续期策略、租约刷新时机、value 唯一性生成方式、以及失败后的退避重试里——别绕开它们,得一个个对齐业务 SLA。











