6.3 数据不一致与连接超时的分诊 本节摘要:两类值班高频报障合审:读到旧数据的合法边界与非法现场的分界,以及 ConnectionLoss、SessionTimeout、Timeout 三种报错各自的病根与分诊路径。证据链主角是四字命令、会话表与 GC 日志。 案发现场一:刚写的数据读不到? 客服工单:配置后台刚改完参数,业务机器上的新配置"时灵时不灵"。这类报障十有八九不是丢数据,而是把顺序一致当成了立即一致。先把合法边界钉死(1.3 节的承诺书在此兑现):读请求由各节点本地应答,被读节点可能落后 Leader 几十毫秒;同一客户端的读写有序,但写方客户端 A 的成功返回,并不保证读方客户端 B 的下一次读立即可见——B 连的若是落后副本,读到旧值合法。
本节摘要:两类值班高频报障合审:读到旧数据的合法边界与非法现场的分界,以及 ConnectionLoss、SessionTimeout、Timeout 三种报错各自的病根与分诊路径。证据链主角是四字命令、会话表与 GC 日志。
客服工单:配置后台刚改完参数,业务机器上的新配置"时灵时不灵"。这类报障十有八九不是丢数据,而是把顺序一致当成了立即一致。先把合法边界钉死(1.3 节的承诺书在此兑现):读请求由各节点本地应答,被读节点可能落后 Leader 几十毫秒;同一客户端的读写有序,但写方客户端 A 的成功返回,并不保证读方客户端 B 的下一次读立即可见——B 连的若是落后副本,读到旧值合法。
合法与非法的分界线一条:持续读旧值超过秒级,或重连后读到比之前更旧的值,才是真故障。前者多半是某台 Follower 落队(syncLimit 吃满被踢、磁盘慢日志堆积),后者涉嫌会话状态错乱,都要按故障查。
# 分诊第一步:三台各读一次同一节点,比对版本号与值 $ for h in node1 node2 node3; do echo "== $h"; \ echo "" | zkCli.sh -server $h:2181 get /config/db-url 2>/dev/null | head -1; done == node1 jdbc:v2 == node2 jdbc:v2 == node3 jdbc:v1 # 落后者现形:只有 node3 给旧值 # 第二步:看 node3 是否落队(outstanding 高、延迟大即嫌疑) $ echo mntr | nc node3 2181 | findstr /i "outstanding latency" zk_outstanding_requests 12 zk_avg_latency 47 # 正常应在个位数毫秒 # 第三步:修复——落队节点自行重同步;持续落队查盘与网络 $ iostat -x 1 3 # 观察事务日志所在盘的 util 与 await
合法旧值的最后一块拼图是 sync:对强一致读有要求的客户端,读前先执行 sync 把自己的连接对齐到 Leader。注意它是确认"我可见的历史追平",不是全集群刷新命令。
客户端报障日志里的三种"超时"常被混为一谈,病根完全不同。ConnectionLoss:TCP 层断开——网络闪断、服务端重启、连接被防火墙掐断。SessionTimeout(expired):租约吊销——客户端停顿超过会话超时(典型是 Full GC),或断连时间跨过了租约终点。操作超时:会话健在,单笔请求迟迟无应答——多数派写被磁盘 fsync 拖慢,或服务端过载。
分诊树(按报错关键字走) ├── ConnectionLoss 频发 │ ├── 集群侧在换届(日志有 LEADING/LOOKING)→ 换届期间的正常表现,关注换届频率 │ ├── 固定时间点出现 → 查防火墙连接老化配置与空闲超时 │ └── 随机出现且伴随网络设备告警 → 链路质量问题 ├── Session expired 频发 │ ├── 应用侧有长时间 GC / 线程停顿 → 扩 sessionTimeout 或治 GC │ ├── 应用侧阻塞在长临界区(心跳线程被占)→ 修代码:心跳是 SDK 自管线程,检查是否被业务抢用 │ └── 会话超时配置两端不一致 → 按 2.3 节协商边界复核 └── 单笔操作超时 ├── 写超时 → 查 fsync 延迟与日志盘负载(4.1 节的写路径瓶颈) ├── 读超时 → 查该节点 outstanding 与羊群效应(6.2 节) └── 全节点全超时 → 查集群容量与误用(大节点、高频轮询)
会话抖动案的最难一段是"客户端看起来活着,租约却被吊销"。完整证据链需要把服务端时钟线与客户端时钟线对齐:
时间线对齐示例(一次 45 秒 Full GC 导致的锁丢失) 服务端:10:00:00 最后一次心跳 | 10:00:30(sessionTimeout=30s)租约到期 10:00:30 清算:删临时节点 lock-0000000012 | 下一序号实例获锁 客户端:10:00:05 GC 开始(STW,心跳线程冻结) 10:00:50 GC 结束,SDK 发现已断连,尝试重连 10:00:51 收到 expired —— 旧锁节点已不在名下,若业务不知情即双跑 对账要点:GC 开始时间 + sessionTimeout <= 服务端清算时刻,即坐实 GC 是死因
修复优先级:先治 GC(45 秒的停顿本身就是更大的故障);治不动就放宽 sessionTimeout 到覆盖最大停顿(4.4 节的原则);锁场景再叠加"临界区操作前重验持有权"(6.1 节三宗罪的第三宗)。三层防御各挡一段,缺一层就有一个窗口。
本节的命令值得收进值班手册:stat 与 mntr 看角色、延迟、outstanding(1.3 与 2.3 节已多次出场);dump 看会话与临时节点归属(4.4 节);cons 看单会话活性(3.3 节);三台分别 get 比对版本号找落队者。四字命令的完整清单在生产环境建议按白名单开放——排查工具也可能是攻击面,权限话题 4.5 节已有交代。
问:能不能干脆让所有读都走 Leader,一劳永逸? 可以配置(读也强制走 Leader),但吞吐上限立刻收缩到单机,等于放弃了 ZooKeeper 的读扩展设计。业务里真正需要强一致读的点通常很少,按需 sync 更划算。
最后一案转向"治未病":监控该看什么,优化该按什么顺序动刀。
问:值班时手头没有监控面板,最快的分诊顺序是什么? 三条命令按序走:先 stat 看角色是否换届中,再 mntr 看 outstanding 与延迟,最后对三台分别 get 同一节点比对版本。三十秒内能把"换届、过载、落队"三大类隔离开,剩下的按 6.3 节分诊树深入。建议把这三条做成值班手册的第一页。