两块积木造一把锁
协议课结业,进入装配车间。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 多了一层保险。
| 设计要素 | Redis | ZooKeeper | etcd |
|---|---|---|---|
| 防死锁载体 | TTL + 看门狗 | 会话 + 临时节点 | 租约 + keepalive |
| 排队公平 | 需 FairLock 附加实现 | 顺序节点序号 | CreateRevision |
| 等待唤醒 | pub/sub 广播 | 一次性 Watch 前驱 | 流式 Watch 前驱 |
| 共识背书 | 无(异步复制) | ZAB | Raft |
| fencing 原料 | 无,需自建 | zxid | revision(一等公民) |
三位选手的底牌都翻完了,下一篇把这张表放大成完整选型地图:性能、一致性、可用性、运维成本、生态,五个维度横评,给出「什么场景用哪把锁」的决策树。
评论 (0)