分布式锁正确性:Lease 过期、Fencing Token 与陈旧写拒绝
拿到锁和持续拥有锁不是同一事实。进程暂停、网络分区或续租响应丢失后,旧 owner 仍可能写入;本文让单调 fencing token 在真正受保护的资源处拒绝陈旧修改。
本文要点
- 主题聚焦:TypeScript、DevOps
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
Worker A 获得十秒租约后发生两分钟停顿,协调服务让 Worker B 接管。A 恢复时内存里仍有“持锁”布尔值,并把旧计算结果写进数据库,覆盖了 B 的新结果。锁服务已经正确判定租约过期,业务资源仍被陈旧 owner 破坏,因为它从未验证所有权代际。
乐观锁与悲观锁讨论单个数据系统内的冲突,分布式共识解释协调服务如何复制状态。本篇不重新比较锁算法,而是建立从协调租约到受保护写入的端到端证明。它也不同于浏览器里的Web Locks 跨标签页 Leader:这里的资源跨进程、跨机器,客户端时钟不能充当裁判。
Lease 只授予尝试资格
发布不可变 lockGrant.v1,绑定 resource canonical ID、owner workload identity、request ID、lease/session ID、grant operation root、单调 fenceToken、协调后端 revision、policy generation、issuer/revocation epoch、服务端 TTL 和操作类别。canonical registry 对 digest 签名/MAC,并提供当前 grant 状态 proof 或线性化查询;token 必须来自每个资源的线性化序列,客户端自报 UUID、墙钟和本地计数都不能充当 fence。
schema: lockGrant.v1
resource: invoice-batch/2026-07-19
owner: worker-settlement-7
lease: lease-91
fenceToken: 8842
backendRevision: 1920184
policyGeneration: 12
operationRoot: sha256:51c...
issuerEpoch: 37
grantProof: sig:v4:8bd...
statusProof: HELD@revision-1920184
state: HELD
本地状态为 REQUESTED -> HELD -> RENEWING -> HELD,续租或查询结果不确定时进入 OWNERSHIP_UNKNOWN,服务端确认过期后为 EXPIRED,主动释放成功为 RELEASED。UNKNOWN 期间停止产生新效果,但不能假定已经发出的写入被撤销。获取响应丢失时,只有能按同一 request ID 查询 grant 才继续;否则等待旧尝试过期并对账,不能创建第二身份同时运行。
ZooKeeper 的临时顺序节点和 etcd 的 session/mutex 可以协调候选者,但它们不会自动让另一个数据库拒绝旧进程。适配器必须把可单调比较的 token 随每次 mutation 传到资源端。若某下游只能接收“locked=true”而不能原子保存最高 token,它不具备 fenced write 能力,只能使用单 writer 架构或幂等、可补偿的窄操作。Redis 在分布式系统的角色也不能把缓存可用性替代资源端条件写。
真正的接受点是资源写入
唯一接受点 applyFencedMutation 先用固定 trust bundle 验签/MAC,并校验短时状态 proof 或线性化查询 canonical registry。grant digest 必须精确绑定 resource、owner、lease、operation root、fence 和 policy generation;调用方 workload identity 必须等于 owner,registry 状态必须为当前 HELD/not-revoked issuer epoch,失败时不得改 highestFence。认证后,资源事务维护 highestFence、该 fence 的 root、lastSegmentSeq 和逐段 receipt:更大 fence 只接受 segmentSeq=1,相等 fence 只接受同 root 下一段。已接受段相同 payload 重放返回原 receipt,异 payload 隔离,更小 fence 一律 STALE_OWNER;业务变化、序号和 receipt 同事务写入。
锁的释放不是业务完成。A 写入超时而租约随后过期时,结果进入 MUTATION_UNKNOWN;恢复者先按 operation ID 查资源 receipt。查到接受则复用,证明未发生才由新 fence 提交新 operation,查不到则人工或领域对账。这个边界与多 Agent 任务 Claim相似,但数据库、对象存储和第三方 API 必须分别提供等价 fence 或稳定效果查询。
长任务把大批次切成小段,所有段共享 grant operation root,但使用严格递增的 segmentSeq 并在每次提交重验 fence;并发算出的段只能按合同顺序接受。下游异步 worker 同时携带 fence、root 和 segment。若新 owner 已把 highestFence 提高,旧 owner 即使拿着尚未使用的后续序号也会被拒;协调集群恢复旧快照造成 token 回退时触发 FENCE_REGRESSION,不得清空最高值恢复流量。
注入暂停、分区与代际倒退
故障矩阵包含 owner 停顿越过 TTL、续租响应丢失、释放与新获取交错、segment 乱序/重复/异 payload、旧消息迟到、协调后端恢复旧快照、事务 ACK 丢失,以及伪造超大 token、篡改 grant digest、跨资源重放 proof 和使用已撤销 issuer epoch。断言伪造/篡改/跨资源 proof 接受为 0,认证失败污染 highestFence 为 0,过期 owner、段跳号和 token 回退接受写均为 0。
门禁要求十万轮时序测试陈旧写与伪造 grant 接受均为 0,canonical grant 认证和 fence 检查覆盖受保护 mutation 的比例 100%,锁交接 p99 小于预算,UNKNOWN 在 15 分钟内对账。上线先 shadow 验证 proof/token,再拒绝单个低风险资源,随后扩大。回滚可切上一协调客户端,但资源端 proof/fence 验证永不关闭,trust bundle 只能受控轮换,新客户端还必须从大于现存最高值的区间继续;否则暂停写入。
Related