22.3 故障检测器与 φ 值

一句话

故障检测器不可能又 “快” 又 “准”:超时越短检测越快、误判越多;φ accrual 把 “二值猜疑(死/活)” 换成 “连续怀疑度”,让上层按风险做决策,而不是被一个魔数超时绑架。

四种检测器(按能力分)

类型 保证 能否误判 典型实现
完美(P) 死了必报、活着不报 不能 同步系统 + 完美信道
最终完美(◇P) 最终不误判 中途可以误判 大多数超时检测器
强(S) 死了必报,活着的可能被报死 可以 广播心跳
最终弱(◇S) 最终能报出失败者 可以 gossip/φ accrual

关键:实际系统都用 ◇P/◇S——因为 “最终” 是最弱但可实现的要求;能否落地,取决于 “误判多久能纠正”。

实测:超时倍数与误判率(100 节点,RTT 均值 50ms 的重尾分布)

超时 误判率 平均检测延迟
2× 均值(100ms) 13.63% 150ms
3× 均值(150ms) 5.05% 199ms
5× 均值(250ms) 0.66% 297ms

这组数字是 “故障检测器的本质权衡”:把误判从 13.6% 压到 0.66%,代价是检测延迟从 150ms 涨到 297ms。没有 “又准又快” 的参数,只有 “按业务选一个点”。

误判的代价 = 不必要的切主/迁移/告警
漏检的代价 = 请求打到死节点、延迟堆积
选择依据:哪一侧更贵

φ accrual:把超时变成概率

φ(t) = -log10(P(心跳间隔 ≥ t | 节点正常))
φ = 1  → 约 10% 把握“它还没死”
φ = 8  → 约 1e-8,几乎可以断定死亡
φ = 12 → 1e-12,通常作为“确认死亡”阈值
// 简化实现:滑动窗口估计心跳间隔分布,输出 φ
func (d *Detector) Phi(elapsed time.Duration) float64 {
    p := d.window.Survival(elapsed)   // P(间隔 > elapsed | 正常)
    if p <= 0 { return math.Inf(1) }
    return -math.Log10(p)
}
// 上层策略:φ > 8 报警,φ > 12 摘除节点,而不是 timeout=3s 一刀切

好处:不同节点、不同网络条件下同一个 φ 阈值含义一致;坏处是需要估计分布(样本少时不准)。

反模式

反模式 后果
全局固定 timeout=3s 跨机房误判、同机房漏检
用 “一次超时” 就判定死亡 抖动导致频繁切主
不做 “检测延迟” 监控 只知道误判,不知道真故障发现得多慢
φ 阈值只调不测 数字好看,实际误判未下降
心跳与业务共用连接 业务拥塞 → 心跳超时 → 误判雪崩
检测器无 “恢复” 路径 节点恢复后仍被当死节点,流量回不去

三条铁律

🔑 检测器是 “概率判断”,不是 “事实”;上层必须能容忍误判。

🔑 超时/φ 阈值要按 “误判成本 vs 漏检成本” 选。

🔑 心跳通道与业务通道隔离,否则拥塞会伪造 “大面积故障”。

自检

Q1 为什么 “完美检测器” 在异步系统里不存在?

▸ 答案:因为异步模型下 “慢” 与 “死” 不可区分:任何有限超时都可能把 “只是慢” 判成 “死”(误判),而要避免误判就得等无限久(无法检测)。完美检测器需要同步假设(延迟有界)。

Q2 实测里 2× 超时的误判率 13.63%,为什么这么高?

▸ 答案:因为 RTT 是重尾分布(本实验用指数分布模拟):少数请求的延迟远超均值,2× 均值(100ms)覆盖不了尾部,于是这些 “只是慢” 的节点被误判。真实网络中跨机房、GC 停顿、重传都会制造长尾。

Q3 φ 值比固定超时好在哪?

▸ 答案:好在 “可比较、可解释”:φ=8 在任何节点上都表示 “约 1e-8 的存活概率”,而 timeout=3s 在不同网络下含义完全不同。它把 “调参” 变成 “选风险阈值”,也让上层可以按业务分级(报警 8、摘除 12)。

Q4 误判为 “死” 之后,系统会付出什么代价?

▸ 答案:取决于系统动作:切主(短暂不可用 + 可能脑裂)、迁移(重启、缓存冷启动)、摘除节点(容量下降、流量重分布)。误判越频繁,这些动作越频繁,最终表现为 “系统在健康与故障之间反复横跳”。

Q5 如何降低 “心跳拥塞 → 误判” 的连锁?

▸ 答案:三条:独立通道/独立线程(心跳不排在业务队列后面)、优先级标记(DSCP/QoS)、本地探测 + 多路径(不要只依赖一条链路)。另外要给心跳留 “抖动预算”,不能按理论 RTT 设超时。

Q6 检测器如何做到 “最终正确”?

▸ 答案:靠 “持续探测 + 状态机”:节点被怀疑后不立即永久摘除,而是保留探测;一旦恢复心跳,经过 “观察期/半开” 再重新纳入。这样误判是临时的,系统最终收敛到正确状态(◇P 的 “最终”)。

Q7 为什么说 “检测器的参数是 SLO 的一部分”?

▸ 答案:因为参数直接决定 “故障发现多快、误判多少”,进而决定可用性与延迟:超时太短 → 误切主;太长 → 请求打到死节点。它应该和 SLO 一起评审,而不是留在某个配置文件里由个人经验决定。

关联