redis分布式锁必须用set key value ex seconds nx原子命令,因setnx+expire非原子,进程崩溃会导致锁永久残留;释放锁需lua脚本校验value后删除,避免误删他人锁。

Redis 分布式锁:别用 SETNX + EXPIRE 两步走
直接调 SETNX 加锁再单独 EXPIRE 是典型错误,中间一旦崩溃或网络中断,锁就永久卡住。必须用原子命令 SET key value EX seconds NX —— 在 go-redis/v9 中对应 client.SetNX(ctx, key, value, ttl)。
锁值不能是固定字符串(比如 "1"),得是全局唯一标识,推荐用 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,否则可能路由到不同节点,锁完全失效 - 业务耗时不确定时,需额外起 goroutine 调
PEXPIRE续期,或改用go-redsync内置租约机制 -
go-redsync默认不校验 client ID,得手动加redsync.WithValue(...)或 patchMutex.Unlock流程
etcd 分布式锁:CAS + Lease 才算真正可靠
etcd 的事务模型天然适合做锁,服务端保证「检查条件 + 写入」一步完成,不靠客户端拼逻辑。加锁必须用 Txn:先 Compare 目标 key 的 version 是否为 0(即不存在),再 OpPut 并绑定 lease ID。
lease ID 必须由 client.Grant(ctx, ttl) 向 etcd 申请,不能自己生成时间戳或随机数——否则续期和自动清理都失效。
- 续租要主动调
client.KeepAliveOnce(ctx, leaseID),且必须处理context.DeadlineExceeded和连接断开错误,失败后应主动Revoke - 抢锁失败时,用
WithPrevKV能直接拿到当前持有者的value,方便排查谁卡住了锁 - etcd 的 Raft 协议保障强一致性,主从切换、网络分区下锁行为可预测,没有 Redis 异步复制丢锁风险
ZooKeeper 分布式锁:临时顺序节点不是万能的
ZooKeeper 不需要客户端维护心跳,靠 Session 自动回收临时节点,天生防死锁。每个客户端在父路径下创建临时顺序节点(如 /locks/lock-0000000001),最小序号者获锁。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
未获锁的客户端只监听前一个序号节点(如 lock-0000000001 监听 lock-0000000000),避免“羊群效应”。
-
go-zookeeper的Lock结构体已封装好生命周期,但NewLock传入的path必须是已存在的持久节点,否则会 panic - Watcher 回调里不能做耗时操作,建议只触发一次
Lock()重试,否则可能堆积大量 goroutine - ZooKeeper 性能低于 Redis,部署运维成本高,除非已有 ZooKeeper 基础设施,否则不建议新项目引入
sync.Mutex 在微服务中完全无效
sync.Mutex 只锁当前进程内存,其他 pod、其他服务器上的 Go 程序压根感知不到。多实例部署下它等于没锁。
生产环境能用的分布式锁,必须依赖外部存储的原子能力——目前只有 redis、etcd、zookeeper 三类方案真正成熟。选型时别只看性能,得看你的系统是否已重度依赖其中某一个组件。
etcd 适合强一致性要求高的链路(比如金融交易、库存扣减);Redis 更适合高吞吐、容忍短暂不一致的场景(比如缓存预热、定时任务抢占);ZooKeeper 则适合已有生态、且需要公平性保障的系统。
最容易被忽略的是锁粒度和续期失败后的兜底策略——锁太粗会拖慢整体并发,续期失败不处理会导致业务中途被强制释放锁,数据错乱往往就发生在这几秒里。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










