故障检测机制:心跳检测、超时阈值与健康检查

各位同学,今天我们来聊聊故障检测机制。说白了,就是怎么知道下游的报价服务还活着。

我见过不少系统,报价路由看起来跑得挺欢,结果某个节点挂了半天,流量还在往里打。用户报错,业务方投诉,运维背锅。嗯,这种场景我经历过太多次了。

所以,一个靠谱的故障检测机制,是报价路由故障转移的基石。今天我把这块掰开揉碎了讲。

一、心跳检测:主动 vs 被动

心跳检测,就是定期确认对方是否存活。我个人习惯把它分成两种:主动心跳和被动心跳。

1. 主动心跳(Push 模式)

每个报价节点,每隔几秒主动向路由中心发一个「我还活着」的信号。

// 伪代码示例:主动心跳上报
while (true) {
    Thread.sleep(5000); // 每5秒发一次
    sendHeartbeat("node-01", "alive", System.currentTimeMillis());
}

这种模式的好处是实时性高。我在项目中遇到过,某个节点网络抖动,主动心跳能第一时间把异常报上来。但要注意,如果心跳包太多,会占用带宽。我曾经见过一个团队,每1秒发一次心跳,结果把路由中心的CPU打满了。

避坑指南:我曾经接手过一个系统,主动心跳的线程和业务线程共用线程池。结果业务高峰期,心跳发不出去,路由中心以为节点挂了,直接切走流量。后来我强制要求:心跳线程必须独立,优先级不能低于业务线程。

2. 被动心跳(Pull 模式)

路由中心主动去拉取每个节点的状态。有点像「你不过来,我就过去看看你」。

// 伪代码示例:被动心跳探测
for (String node : nodeList) {
    boolean alive = checkNode(node, 3000); // 3秒超时
    if (!alive) {
        markAsDown(node);
    }
}

被动心跳的好处是路由中心完全掌控节奏。但坏处也很明显——如果节点太多,轮询一圈下来,延迟会很高。你想想看,100个节点,每个3秒超时,一轮就是300秒。这期间节点挂了都不知道。

我的建议:实际项目中,我通常采用「主动+被动」混合模式。主动心跳用于快速上报,被动心跳作为兜底校验。两者结合,既保证实时性,又防止单点故障。

二、超时阈值设置:不是拍脑袋定的

超时阈值怎么设?很多同学直接写死3秒、5秒。说实话,这是给自己埋坑。

我总结了一个经验公式:

参数 推荐值 说明
心跳间隔 3~5秒 太频繁浪费资源,太慢延迟高
超时阈值 心跳间隔 × 3 允许一次丢包+一次重试
连续失败次数 3次 避免网络抖动误判

为什么会这样?我举个例子。假设心跳间隔5秒,超时阈值设成5秒。网络稍微抖一下,丢了一个包,节点就被判定为挂了。这太敏感了。

我个人习惯把超时阈值设成心跳间隔的3倍。比如心跳5秒一次,超时15秒。连续3次超时,才判定节点异常。这样既不会误判,也不会等太久。

核心要点:超时阈值不是固定值,要根据网络延迟、业务容忍度动态调整。我曾经在跨机房场景下,把超时阈值从3秒调到了10秒,因为公网延迟本身就高。

三、健康检查接口设计

健康检查接口,不是简单地返回一个200就完事了。我见过太多「假健康」的案例。

一个合格的健康检查接口,应该包含以下信息:

// 健康检查接口返回示例
{
    "status": "UP",
    "timestamp": 1700000000000,
    "details": {
        "cpu": 0.45,          // CPU使用率
        "memory": 0.67,       // 内存使用率
        "connections": 128,   // 当前连接数
        "lastHeartbeat": 1700000000000,
        "errorCount": 0       // 最近1分钟错误数
    }
}

为什么要返回这么多信息?因为路由中心需要根据这些数据做决策。比如某个节点CPU飙到90%,虽然还活着,但响应速度肯定慢。这时候就应该把它从可用列表里摘掉。

避坑指南:我曾经遇到过一个项目,健康检查接口里调用了数据库。结果数据库挂了,健康检查接口也挂了,路由中心以为节点挂了,把流量切走。但实际上业务接口还能正常提供服务。后来我规定:健康检查接口只能检查自身状态,不能依赖外部服务。

四、异常状态判定逻辑

有了心跳数据和健康检查结果,接下来就是判定逻辑了。我把它总结成一张状态机图:

健康 (UP) 可疑 (SUSPECT) 故障 (DOWN) 恢复中 (RECOVERING) 1次超时 连续3次超时 心跳恢复 手动/自动恢复 连续3次心跳成功 再次超时 报价节点状态判定逻辑 基于心跳检测 + 健康检查的综合判定

这张图我用了很多年。核心逻辑就一句话:不要因为一次失败就判定死亡,也不要因为一次成功就判定复活

具体判定规则如下:

  1. 健康 -> 可疑:第一次心跳超时或健康检查异常。这时候不切流量,只是标记为可疑。
  2. 可疑 -> 故障:连续3次超时,或者健康检查返回严重错误(比如CPU 100%)。这时候立即摘掉节点。
  3. 故障 -> 恢复中:运维手动恢复,或者自动恢复策略触发。进入恢复中状态。
  4. 恢复中 -> 健康:连续3次心跳成功,且健康检查通过。这时候才把节点加回来。
  5. 恢复中 -> 故障:恢复过程中再次超时,说明节点不稳定,直接打回故障状态。
我的经验:这个状态机设计,核心是「怀疑一切,验证一切」。不要轻易相信一个节点,也不要轻易放弃一个节点。我曾经在双11大促期间,就因为状态机设计得太激进,导致大量节点被误判,差点引发雪崩。后来改成这个逻辑,稳如老狗。

五、总结一下

故障检测机制,说白了就是三个问题:

  • 怎么知道对方活着?—— 心跳检测(主动+被动)
  • 多久算死?—— 超时阈值(3倍心跳间隔)
  • 死了怎么办?—— 状态机判定(健康->可疑->故障->恢复中)

嗯,今天就讲到这里。这些内容看起来简单,但实际落地时坑很多。我建议你们回去后,先画一张自己系统的状态机图,然后对照着代码走一遍。你会发现,很多地方其实没你想的那么完美。

最后一个小技巧:健康检查接口一定要加缓存。比如把CPU、内存数据缓存1秒,避免每次请求都去读/proc/stat。我曾经因为这个,把一个节点的健康检查接口从1ms打到了100ms,差点被运维打死。

公众号:蓝海资料掘金营,微信deep3321