发布于2026-07-09 阅读(0)
扫一扫,手机访问
直接说结论:etcd 分布式锁必须组合 Lease、Txn、Watch 和有序 key 设计,仅用 Put+WithLease 或 CompareAndSwap 无法保证互斥;因为 Put 不检查 key 是否存在,多个客户端会同时写入成功,需用 Txn 的 Compare(Version, "=", 0) 实现“仅当 key 不存在时写入”,并配合 lease 续期、Watch 前序 key 实现公平唤醒与自动清理。

先说几个核心判断:不能用 clientv3.Put 裸写 key,也不能只靠 CompareAndSwap 事务就完事——必须组合 Lease、Txn、Watch 和有序 key 设计,否则两个客户端会同时抢锁成功。
Put + WithLease 不能直接当锁用很多团队之前踩过的坑就是下面这种写法:
_, err := client.Put(ctx, "/lock/order", "holder-123", clientv3.WithLease(leaseID))
问题出在哪?根本没做前置检查。多个客户端并发执行这段代码,都会成功写入,然后所有人都以为自己拿到了锁。这不就乱套了。
WithLease 只负责过期清理,不参与竞争判定Txn + Compare(Version(key), "=", 0)Txn + Compare + WithLease正确姿势是把“检查 + 写入”打包成一个事务:
txnResp, err := client.Txn(ctx).If(clientv3.Compare(clientv3.Version("/lock/order"), "=", 0)).Then(clientv3.OpPut("/lock/order", "holder-123", clientv3.WithLease(leaseID))).Else(clientv3.OpGet("/lock/order")).Commit()
这里有几个关键细节要注意:
Compare(clientv3.Version(key), "=", 0) 表示“该 key 当前 version 是 0”,即从未被创建过Then 分支成功才真正写入,且绑定 lease;Else 分支可顺便读出当前持有者,方便排查client.Grant(ctx, ttl) 申请,不能手动生成字符串或时间戳——否则续期失败、自动清理失效Watch 监听前序 key那么遇到抢锁失败时,应该怎么做?etcd 锁的公平性靠有序 key 实现,比如用时间戳+随机数生成唯一后缀:/lock/order/1678901234567890123。抢锁失败时,可不是睡几百毫秒再试,而是:
/lock/order/ 前缀下的 key,按 key 字典序排序/lock/order/1678901234567890123,就监听 /lock/order/1678901234567890122)client.Watch(ctx, prevKey, clientv3.WithPrevKV(), clientv3.WithRev(revision)),监听其删除事件需要警惕的是:Watch 可能断连,必须捕获 context.DeadlineExceeded 或连接错误,并用 WithRev 指定起始 revision 重连,否则漏事件。
KeepAlive,释放锁必须 Revokelease 过期不是 key 删除延迟,而是 key 立刻失效。但客户端得主动保活:
client.KeepAlive(ctx, leaseID),它返回 <-chan *clientv3.LeaseKeepAliveResponsenil,说明 lease 已过期,应立即放弃锁client.Revoke(ctx, leaseID)——etcd 会自动清理所有绑定该 lease 的 keyRevoke:如果锁已被别人抢走,你的 Revoke 会误删别人的 key还有一个容易忽略的点:goroutine 泄漏。每个锁实例要绑定自己的 ctx,并在释放锁时 cancel(),否则 KeepAlive 协程一直挂着。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8