连载中 14/20

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

2026-07-02 · 4598 阅读 · 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)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10100 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9872 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9293 阅读 · 21 评论 · 287 赞