连载中 11/20

ZooKeeper 的坑:会话超时与幽灵持有人

2026-06-30 · 4624 阅读 · 0 评论 · 0 赞

GC 停顿二十秒之后

上一篇把 ZK 锁夸了个遍,这一篇泼冷水。事故现场还原:客户端 A 持锁干活,sessionTimeout 设 20 秒;干到一半 Full GC 停顿 25 秒——心跳停了 25 秒,超过 20 秒阈值,ZK 服务端判定会话过期,临时节点删除,锁按设计自动释放——协议完全正确。客户端 B 顺位拿到锁,开始干活。GC 结束,A 复活,A 的代码一路往下跑,它不知道自己「死过一回」——临界区里出现两个持有人。这个场景与第 5 篇 Martin 批评 RedLock 的 GC 场景完全同构:「进程被冻结过」这件事,冻结中的进程永远无法自知。ZK 没有魔法躲过它,能做到的只是「让 A 尽快知情」。

坑一:幽灵持有人与知情机制

A 复活后,下一次对 ZK 的任何 API 调用都会收到 SessionExpiredException——会话过期是服务端状态,A 一碰 ZK 就知道。但注意「一碰 ZK 就知道」的前提是 A 会去碰:如果 A 的剩余业务全程不再访问 ZK(比如锁内就是写数据库),它到死都不知道自己成了幽灵。工程上的缓解三板斧:其一,注册 Curator 的连接状态监听(ConnectionStateListener),LOST 事件立刻中断临界区业务(设置中断标志、回滚已做的操作);其二,临界区里的关键写操作前后各查一次会话有效性;其三——也是最诚实的一句——涉及资金库存的强一致场景,锁之上必须再叠 fencing token(第 16 篇),锁的知情机制只能降低概率,不能改变结局

坑二:sessionTimeout 的两难

会话超时是「活性判死刑」的刻度,设短设长都是坑。设短(比如 10 秒):持有人死得干净利落、锁交接快,但网络抖一下、GC 停几秒就误判,健康的持有人被频繁处决,锁易主率飙升;设长(比如 60 秒):误判少了,但真死的持有人要拖 60 秒才释放锁,故障恢复时间被拉长。ZooKeeper 的折中是协商:客户端提议,服务端限制在 tickTime 的 2 到 20 倍之间。经验值是 30 到 40 秒起步,同时把「GC 停顿必须小于会话超时」写进运维红线(JVM 调优、堆内存别太抠),从源头减少「假死」。

坑三:读到的可能是旧账

第 8 篇提过一句「读请求走各节点本地服务」——这里补齐杀伤力。锁排位要 getChildren(读),它走的是连接到的那个 Follower 的本地数据,可能落后于 Leader 几十毫秒:锁刚被释放(Leader 已确认删除),你的 Follower 还没同步到,你看到的子节点列表里前驱还在,白等一轮。多数场景只是慢一点,无伤大雅;强迫症可以用 sync 命令强制读到 Leader 已确认的最新状态,代价是多一次往返。Curator 内部已做了合理处理,这个坑更多是提醒:别把 ZK 当强一致的读库用,它的读是顺序一致,不是线性一致

坑四:性能天花板,坑五:裁判不能倒

坑四是吞吐:每次抢锁 = 创建节点(写)= 走一遍 ZAB 过半提交,QPS 千级封顶。第 10 篇说过它不适合秒杀,这里补一刀更隐蔽的:ZK 集群往往一身兼多职(注册中心 + 配置中心 + 锁),高频锁操作会拖慢整个集群,把注册发现、配置推送一起拖下水——裁判被一个球员累垮,全场停赛。锁量大时要么拆独立 ZK 集群,要么把高频竞争交给 Redis。坑五是运维:ZK 本身是个需要伺候的分布式系统——3 或 5 节点部署、JVM 调优、快照与事务日志管理、版本升级;它挂了,依赖它的注册、配置、锁全部瘫痪。引入 ZK 不是装个 Redis 那么轻巧,是把一个需要 SRE 的系统请进了机房

根因缓解
幽灵持有人进程被冻结后不自知状态监听中断业务 + fencing 兜底
会话误判抖动/GC 冒充死亡超时 30-40s + JVM 红线
读到旧数据读走 Follower 本地sync / 用 Curator
吞吐天花板写锁必走过半提交低频场景用 ZK,高频给 Redis
运维负担自身是需要伺候的集群专职运维或托管服务

盘点完发现一个耐人寻味的对称:Redis 锁的坑在「复制不可靠」,ZK 锁的坑在「活性误判」——前者是安全性的裂缝,后者是可用性的代价,两者背后是同一个幽灵:不可靠的网络与不可预测的停顿。把这个幽灵正面拿下的是第三位选手 etcd,它靠的协议叫 Raft——ZAB 的工业化通用版。下一篇从选举讲起:一票、一任期、一份日志,Raft 的入门三件套。

503

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

#ZooKeeper#会话超时#幽灵持有人#GC停顿#sync#性能天花板

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 4 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞