分布式锁、租约与 Fencing Token
本文基于模型知识整理(生成时未联网核对),关键结论建议对照经典文献复核。
一句话定义
分布式锁用共享协调(共识系统或租约)保证互斥,但进程可能"持锁假死"后复活继续操作旧资源,因此安全的锁必须配合 fencing token:让存储层拒绝持过期令牌的请求,把互斥裁决下沉到资源方。
为什么重要
"加个分布式锁"是工程里最常被低估的操作:GC 停顿、时钟跳变、网络分区都能让一把看似有效的锁变成双持。理解锁的正确性边界与 fencing 的必要性,是避免数据双写事故的关键;Redlock 之争是这一主题最好的教材。
前置知识
kp-019(共识)、kp-003(时钟不可信)、kp-006(超时与未知态)。
核心概念
- 锁的三要求:互斥(任意时刻至多一个持有者)、无死锁(持有者崩溃后锁可释放)、容错(协调者部分失效不影响正确性)。
- 租约(lease):带有效期的锁;持有者需在到期前续约。把"锁无限持有"改为"时间窗口持有",便于失效回收,但引入时钟依赖与续约竞态。
- 假死复活(GC pause / 停顿):持锁进程停顿超过租约期,锁被他人取得;旧进程恢复后继续执行——它不知道自己已过期。
- fencing token:锁服务在每次授锁时发放单调递增的令牌;资源存储层记录见过的最大令牌,拒绝令牌更小的写。停顿的旧持有者带着小令牌回来即被拒。
- Redlock 争议:Redis 作者 antirez 的多节点 Redlock 算法 vs Kleppmann 的批评——在无共识日志的多副本锁上,停顿+时钟偏差下仍可能双持,且缺乏 fencing 支持。
原理与机制
为什么"检查过期再操作"救不了你:在"检查自己是否仍持锁"与"操作资源"之间存在停顿窗口;停顿期间锁可能易主、检查结果已过时。任何由持有者自证的方案都过不了这一关——安全裁决必须由资源方依据单调令牌做出,因为存储层在每个请求上做裁决不受客户端停顿影响。
为什么令牌必须单调且由共识服务发:etcd/ZooKeeper 的锁实现天然带全局单调序号(zxid/ModRevision),复用为 fencing token;若锁服务本身无全序(多副本 Redis 无共识日志),令牌就不可靠——这是 Kleppmann 批评 Redlock 的核心:错误不在参数调优,而在缺乏全序来源。
租约与共识的分工:租约(如 etcd lease)给活性(到期自动回收、开销低);共识日志给安全序(令牌单调)。二者结合是现代实现的标准形态;纯时间租约(无全序来源)只适用于可容忍短暂双持且操作幂等的场景。
图示
C1 获锁(token=33) ──GC停顿 2min──► 锁过期 → C2 获锁(token=34) → 写存储(token=34)
C1 恢复 → 写存储(token=33) → 存储拒绝(33 < 34) ✓ 双写被挡下
无 fencing 时: C1 写入成功 → 数据被旧值覆盖 ✗
直观类比
健身房储物柜租约到期后柜子会被重新分配;如果原来的会员晚到了还想开柜,只有"柜子管理系统认新票不认旧票"(fencing)才能避免两人在同一柜里放东西。会员自己喊"我的租约还有效"没用——得柜子说了算。
实例或案例
- etcd 分布式锁(
concurrency包):基于 Raft 版本号的 fencing + lease 续约,是教科书实现。 - 任务调度去重:cron 多实例防重跑——正确做法是数据库唯一键/条件更新(自带 fencing 语义),而非裸 Redis SETNX。
- HDFS NameNode HA:ZKFC 用 ZK 租约做主备切换,同时 JournalNode 拒绝旧 epoch 的写——fencing 在工业界的完整落地。
常见误区
- 误区一:"锁生效了就安全"。锁只在你活着时生效;停顿后的操作必须靠资源方令牌裁决。
- 误区二:"把租约调长就避免假死问题"。更长租约降低误判但延长故障恢复时间,且不能消除停顿超期——只是概率换时间,安全仍需 fencing。
- 误区三:"Redlock 加上时钟同步就严谨"。时钟跳变(kp-003)恰恰不可控;缺乏全序令牌的结构性缺陷不因参数改善而消失。
与其他知识点的关系
自测题
- 为什么持有者"先检查锁还在再写"不可靠?
答:检查与写之间存在停顿窗口,期间锁可能易主;检查结果瞬间失效,安全裁决必须由资源方在每个请求上依据单调令牌做出。
- fencing token 需要什么性质?谁来保证?
答:全局单调且不可伪造;由具备全序的共识服务(Raft/Paxos 日志序号)发放,存储层记录最大已见令牌并拒绝更小者。
- 什么场景可以不用 fencing 直接用租约锁?
答:操作幂等或可容忍短暂双持(如缓存预热、软性任务去重),且对故障恢复速度要求高于绝对互斥。
延伸阅读
- Martin Kleppmann, "How to do distributed locking"(2016,对 Redlock 的系统性批评)。
- Salvatore Sanfilippo, "Is Redlock safe?"(antirez 的回应)。
- HDFS HA 设计文档(fencing 在 JournalNode 的实现)。