Redis、Etcd、ZooKeeper是生产可用的三类分布式锁方案:Redis需用SET原子命令+唯一value+Lua释放;Etcd依赖CAS+Lease;ZooKeeper基于临时顺序节点+Watcher实现公平锁。

单机 sync.Mutex 在微服务里完全没用——它锁不住其他 Pod、其他机器上的 Go 实例。真正在生产跑得起来的分布式锁,必须依赖外部存储的原子能力,目前只有 Redis、Etcd、ZooKeeper 三类方案真正成熟。
Redis 分布式锁:别用 SETNX + EXPIRE 两步写法
这是新手最常踩的坑:先 SETNX 加锁,再单独调 EXPIRE 设过期时间。中间一旦进程崩溃或网络中断,锁就永久残留,形成死锁。
- 必须用原子命令:
SET key value EX seconds NX(go-redis/v9中对应SetNX方法,传time.Duration即自动封装) - 锁值不能是固定字符串,得是全局唯一标识,比如
uuid.NewString(),否则释放时无法校验所有权 - 释放锁必须走 Lua 脚本,确保「读值比对 + 删除」原子执行,脚本长这样:
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end - Redis Cluster 模式下,key 必须带 hash tag,例如
{lock:order}:123,否则可能路由到不同节点导致锁失效
Etcd 分布式锁:CAS + Lease 是最稳的起点
Etcd 的事务模型天然适合做锁,服务端保证「检查条件 + 写入」一步完成,不靠客户端拼逻辑。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 加锁必须用
Txn:先Compare目标 key 的 version 是否为 0(不存在),再OpPut并绑定lease ID -
lease ID必须由client.Grant(ctx, ttl)向 Etcd 申请,不能自己生成时间戳或随机数——否则续期和自动清理都失效 - 续租要主动调
client.KeepAliveOnce(ctx, leaseID),且必须处理context.DeadlineExceeded和连接断开错误,失败后应主动Revoke - 抢锁失败时,用
WithPrevKV能直接拿到当前持有者的value,方便排查谁卡住了锁
ZooKeeper 分布式锁:临时顺序节点 + Watcher 是公平性的保障
ZooKeeper 不需要客户端维护心跳或续期,靠 Session 自动回收临时节点,天生防死锁。
立即学习“go语言免费学习笔记(深入)”;
- 每个客户端在父路径下创建临时顺序节点(如
/locks/lock-0000000001),最小序号者获锁 - 未获锁的客户端只监听前一个序号节点(如
lock-0000000001监听lock-0000000000),避免“羊群效应” -
go-zookeeper的Lock结构体已封装好生命周期,但要注意NewLock传入的path必须是已存在的持久节点,否则会panic - Watcher 回调里不能做耗时操作,建议只触发一次
Lock()重试,否则可能堆积大量 goroutine
选哪个不是看文档写的多漂亮,而是看你的基础设施现状:已有 Redis 就用 Redis,但得补上 Lua 校验和自动续期;已在用 Etcd 做服务发现,那就顺手用它实现锁;对强一致性要求极高、能接受运维成本,ZooKeeper 更稳。最容易被忽略的是锁值唯一性和释放时的原子性——这两个点一错,整个锁机制就形同虚设。

















