连载中 14/20

etcd 分布式锁:一个 Lease 管生死,一个 Revision 排公平

2026-07-02 · 4595 阅读 · 0 评论 · 0 赞

两块积木造一把锁

协议课结业,进入装配车间。etcd 造锁不引入任何新概念,全部用自家的两块积木:Lease(租约)管生死,revision(全局修订号)管公平。先看第一块——Lease 是 etcd 里的独立对象:客户端 grant 一个 30 秒的租约,把 key 挂上去(attach),然后周期性 keepalive 续约;租约到期无人续,挂在它上面的所有 key 被自动删除。对照记忆:ZK 的会话绑定临时节点,Redis 的 TTL 绑定 String——「锁的生死绑定持有进程活性」这个命题,三家三种实现,etcd 的版本是租约。

lease = client.Grant(ctx, 30)                  # 申请 30s 租约
client.Put(ctx, "lock/card-8801/x", "", clientv3.WithLease(lease.ID))
# 之后每 10s(TTL/3)keepalive 续约
# 进程崩溃 → 续约停止 → 30s 后租约过期 → key 自动删除

排队的钥匙:CreateRevision

第二块积木上一篇埋过伏笔:etcd 每次 Put 都分配全局单调递增的 revision,key 的创建版本号(CreateRevision)天然是一张排队号牌。抢锁流程与 ZK 临时顺序节点一一同构:

1. 在同一个 prefix(/locks/card-8801/)下创建带 lease 的 key
   → key 自带 CreateRevision,如 rev=1042
2. Range 列出 prefix 下所有 key,按 CreateRevision 排序
3. 自己最小 → 持锁,进临界区
4. 不是最小 → Watch「上一个 revision」的 key 的 DELETE 事件
5. 收到删除通知 → 回到第 2 步重新排位

逐项对齐:lease 对应 ZK 会话,CreateRevision 对应顺序节点序号,Watch DELETE 对应 Watch 前驱——「只盯前一个」的防惊群设计原样继承。差异在 Watch 的成色:ZK 一次性 Watch 触发即失效,要手动续订;etcd 的 Watch 是流式订阅,从某个 revision 起持续推送、断线可从断点重放——锁排队的代码从几十行缩到几行。公平性也不用再论证:revision 由 Raft 集群仲裁分配,全局唯一、严格单调,号牌本身就是共识的产物

官方封装与一段 Go 代码

日常开发不必手搓流程,官方 clientv3 的 concurrency 包把 Session 与 Mutex 打包好了:

session, _ := concurrency.NewSession(cli, concurrency.WithTTL(30))
mutex := concurrency.NewMutex(session, "/locks/card-8801")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := mutex.Lock(ctx); err == nil {       // 最多等 5 秒
    // 临界区业务
    mutex.Unlock(context.Background())
}

NewSession 内部完成 grant 加后台 keepalive 循环,Lock 内部完成排位加 Watch 前驱——十行以内的锁,协议背书完整。

etcd 锁的坑:换了壳的幽灵还在

别以为换了裁判就没有幽灵。第 11 篇的坑在 etcd 上原样存在:进程 GC 停顿或网络分区超过 TTL,keepalive 停摆,租约过期,key 被删,锁易主;进程复活后自以为还在持锁。缓解思路也一脉相承:keepalive 客户端发现续约失败要立刻上报(session.Done 通道),业务线程监听并中断临界区;强一致场景依然需要 fencing 兜底(etcd 的优势是 fencing 原料现成——CreateRevision 就是单调令牌,第 16 篇细讲)。另外两处小坑:TTL 别设太短(续约请求本身有延迟,TTL 5 秒在抖动网络下误判率陡增),建议 30 秒级别;时钟纪律倒是好消息——租约由 etcd 服务端的单调时钟推进,客户端时钟跳变不影响生死判定,比 Redis 锁的 TTL 多了一层保险。

设计要素RedisZooKeeperetcd
防死锁载体TTL + 看门狗会话 + 临时节点租约 + keepalive
排队公平需 FairLock 附加实现顺序节点序号CreateRevision
等待唤醒pub/sub 广播一次性 Watch 前驱流式 Watch 前驱
共识背书无(异步复制)ZABRaft
fencing 原料无,需自建zxidrevision(一等公民)

三位选手的底牌都翻完了,下一篇把这张表放大成完整选型地图:性能、一致性、可用性、运维成本、生态,五个维度横评,给出「什么场景用哪把锁」的决策树。

503

10 年全栈工程师 · 503咖啡馆主理人

#etcd#分布式锁#Lease#keepalive#revision#concurrency

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 3 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞