连载中 17/20

fencing token:当持有人是个幽灵,下游凭什么拒绝它

2026-07-03 · 3514 阅读 · 0 评论 · 0 赞

一个挡不住的幽灵

本系列已经三次撞见同一个幽灵:第 5 篇 RedLock 论战里的 GC 停顿客户端、第 11 篇 ZK 会话过期后的复活进程、第 7 篇主从切换后的第一个持有人——共同点是锁已经丢了,它自己不知道,业务还在往下游写。第 11 篇给过缓解三板斧:状态监听、主动查询、会话校验——但它们都依赖「客户端去问」,而幽灵的本质恰恰是「它不会去问」。要把这类事故从「降低概率」变成「数学上不可能」,防线必须前移到下游:让存储有能力拒绝过期持有者的写入。这就是 fencing token(栅栏令牌)。

机制:一张只涨不跌的通行证

fencing token 的运行规则两句话:锁服务每次授予锁,发一个严格单调递增的编号(第一次 33,下一次 34……);下游存储记录它见过的最大编号,凡是编号小于记录值的写入,一律拒绝。看时间线:

t1  A 抢到锁,领到 token = 33,开始干活
t2  A 发生 GC 停顿 / 网络分区(自己毫无感知)
t3  锁过期或易主,B 抢到锁,领到 token = 34
t4  B 写下游:携带 token 34 → 下游记录 max_token = 34,写入成功
t5  A 复活,带着 token 33 来写 → 33 < 34,下游拒绝
    ↑ 幽灵的写入被物理拦下,无论 A 是否「知道自己成了幽灵」

妙处在于判决权在数据这一侧:不再依赖持有人自觉(知情机制)或锁服务仲裁(锁的正确性),而是让「最新持有人」的写入天然盖过「过期持有人」——token 的单调性由锁服务保证,比较由存储执行,两个环节都在「清醒」的参与者手里。Martin 对 RedLock 的全部批评,落点就是这句话:时间型锁必须配 fencing token 才能谈安全。

令牌从哪来

三家锁的令牌原料天差地别,第 14 篇的对比表里埋过伏笔。ZooKeeper:锁节点的 zxid 或 stat.version——Curator 的 acquire 之后能拿到节点状态,zxid 全局单调,直接当 token 用。etcd:锁 key 的 CreateRevision——revision 全局单调递增、由 Raft 仲裁,一等公民级原料,etcd 官方文档明确支持这个用法。Redis:没有现成的,要自建——抢锁成功后对同一个计数 key 执行 INCR(Lua 里和 SET NX 打包成原子操作),或者用数据库 sequence。自建 INCR 本身也有单点与持久性问题,但作为「锁 + 兜底」组合里的兜底层,够用;真要较真,资金场景直接换 etcd 更省心。

下游得配合:条件写入是前提

fencing token 的一半成本在下游改造:存储必须支持「带令牌比较的条件写入」。数据库一行代码的事——update account set balance=? where card_no=? and token=?(把 token 当乐观锁字段用,第 16 篇版本号的变体);etcd 用事务原语 compare-and-put 天然支持;ZNode 的 version CAS 也可以。麻烦的是纯 Redis 下游——需要 Lua 把「比较 max_token」和「写入」打包成原子操作。还要诚实交底两条局限:其一,fencing 保证「旧的写不进来」,不保证「新的写一定正确」——业务逻辑 bug 照样出事故,它不是银弹;其二,锁服务整体宕机时发不出新 token,业务不可用——fencing 保护正确性,可用性要靠锁服务自身的高可用(共识派的优势又回来了)。

防线拦截对象位置强度
锁的知情机制丢锁后继续跑的持有者客户端降概率(依赖自觉)
幂等 / 去重同一操作的重复执行业务层强(但需要业务键)
fencing token过期持有者的任意写入存储层数学级(单调性背书)

三条防线各司其职:知情机制减损、幂等防重、fencing 终审——资金级场景三层全上,普通业务做到前两层即可。下一篇落地最后一问:真要引入协调服务,ZK 还是 etcd?部署规模、运维成本、生态绑定,给出云原生时代的现实答案。

☕
503

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

#fencing token#幽灵持有人#单调递增#条件写入#下游校验

评论 (0)

热门推荐

连载中 11/22

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

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

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

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

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

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

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

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

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