18.5 故障检测与切换

一句话

切换 = 检测 + 决策 + 执行(提升 + fence + 改路由);检测的每一个 “更快” 都换来 “更多误判”:超时 100ms 时误判 13.6%,250ms 时 0.7%——选哪档,取决于 “误切的代价” 与 “停服的代价” 谁更大。

切换的四个动作

动作 做什么 失败后果
检测 心跳超时 / 健康检查 / 副本延迟 误判 → 不必要的切换
决策 谁当新主(多数派/优先级) 选错 → 数据丢失或双主
提升(promote) 备库停止重放、开启写入 未追平 → 丢最后一段
fence 旧主 阻止旧主继续写 没做 → 脑裂
改路由 客户端/DNS/VIP 指向新主 缓存 → 部分客户端仍打旧主

关键:“提升” 不等于 “切换完成”,最后两步(fence + 改路由)才决定是否真的安全。

实测:检测超时 vs 误判

3 副本、心跳 RTT 均值 50ms(重尾)、2 万次采样:

检测超时 误判率 真故障发现延迟
100ms(2×均值) 13.61% ≈100ms
250ms(5×均值) 0.70% ≈250ms

13.6% 的误判意味着:每 7 次 “检测到故障”,就有 1 次是误判——如果自动切换,就会频繁 “无故切主”,比不切更糟。

切换代价 = 误判率 × 每次误切的代价(中断 + 脑裂风险)
         + 检测延迟 × 真故障的停机成本
选点:让两者之和最小,而不是“越快越好”

代码:一次安全切换的最小步骤

# 1) 确认备库已追平(位点差 < 阈值)
psql -c "SELECT client_addr, pg_wal_lsn_diff(sent_lsn, replay_lsn) FROM pg_stat_replication;"
# 2) 提升
pg_ctl promote -D /data/pg
# 3) fence 旧主(比“杀掉”更可靠:撤销其写入权限)
pg_ctl stop -m immediate -D /data/pg || true
# 4) 改路由(DNS/VIP/连接串),并让客户端刷新连接池
kubectl patch svc db -p '{"spec":{"selector":{"role":"primary"}}}'

判据:第 1 步不达标就不许提升(宁可不切,不可丢数据);第 3 步不可省(旧主复活会写入)。

反模式

反模式 后果
只做 “提升”,不做 fence 与改路由 旧主继续写 → 脑裂/双写
未追平就提升 丢失最后一批已提交事务
自动切换不设 “冷却期” 抖动时反复切换,雪崩
检测只依赖单一路径(单心跳) 网络抖动即误判
切换后不校验数据一致性 悄悄丢数据,很久后才发现
没有回切方案 新主有问题只能硬扛

三条铁律

🔑 提升前必须确认追平;宁可不切,不可丢数据。

🔑 fence 旧主是切换的一半;不做等于制造双主。

🔑 自动切换要有冷却与人工闸门,避免抖动放大。

自检

Q1 为什么 “检测越快” 不一定越好?

▸ 答案:因为快检测必然用更短超时,而短超时把 “慢” 判成 “死” 的概率更高(实测 100ms 超时误判 13.6%)。误切会带来中断、脑裂风险与客户端重连风暴,代价可能高于 “多等 150ms”。要按 “误判代价 vs 停机代价” 选点。

Q2 为什么 “追平” 是提升的前置条件?

▸ 答案:因为备库可能落后于主库(异步复制)。未追平就提升,意味着主库已提交但备库没收到的数据永久丢失(RPO 受损)。所以切换流程必须是 “先等位点差 < 阈值,再提升”,否则就是拿数据换速度。

Q3 fence 旧主为什么比 “直接杀进程” 更可靠?

▸ 答案:因为进程可能 “假死”(网络分区、GC 停顿):你以为杀了,其实它还在跑并继续写。fence 是从外部撤销其写权限(存储侧 token、VIP 摘除、只读挂载),即使进程活着也写不进去——这是防双主的最后一道闸。

Q4 自动切换的 “冷却期” 解决什么?

▸ 答案:解决抖动放大:网络抖动导致误判 → 切换 → 新主又抖 → 再切,形成雪崩。冷却期(如 5 分钟内不再自动切)给人工介入与状态稳定的时间,避免 “越切越坏”。

Q5 切换后最先验证什么?

▸ 答案:三件事:数据位点(新主是否包含最后已提交事务)、写入可用(应用能写、无只读锁)、旧主已 fence(无法再写)。前两个决定 “能不能用”,第三个决定 “会不会分叉”。

Q6 什么时候该 “人工切换” 而不是自动?

▸ 答案:当 误判代价高、或状态判断复杂 时:跨地域切换(网络分区难判定)、多副本一致性要求高(需确认多数派)、有明显 “抖动史” 的链路。人工切换慢,但不会因误判制造更大事故。

Q7 如何演练一次切换而不影响生产?

▸ 答案:三条:影子环境做全流程(含 fence 与改路由)、生产只读演练(切到备库只读,验证连接与延迟)、混沌工程注入 “主库失联” 看监控与自动化反应(见 56.2 故障演练)。

关联