2. 故障检测机制:心跳检测、超时机制、健康检查端点、日志异常检测

故障检测,说白了就是给流水线装个“心电图”。

你想想看,一条流水线跑得好好的,突然某个环节卡住了,或者干脆挂了。如果没有检测机制,后面的任务还在傻等,整个系统就堵死了。我见过太多线上事故,根源就是“没人知道它已经死了”。

这一章,我们聊聊四种最常用的检测手段。它们各有各的脾气,组合起来用才靠谱。

2.1 心跳检测:最朴素的“你还活着吗?”

心跳检测的原理很简单——被监控方定期发一个“我还活着”的信号。监控方如果在规定时间内没收到,就判定它挂了。

核心要点:心跳不是万能的,但它是最快发现“进程级死亡”的手段。

我在项目中遇到过一个问题:某个微服务进程还在,但已经死锁了。心跳还能发,业务却处理不了。这就是心跳的盲区——它只能检测“进程是否在跑”,不能检测“业务是否正常”。

心跳的两种模式

模式 原理 适用场景 缺点
Push 模式 被监控方主动上报 内网、低延迟环境 被监控方挂了,心跳也断了
Pull 模式 监控方主动拉取 跨网络、需要统一管理 监控方压力大,有延迟

我个人习惯在流水线中用 Push 模式。因为流水线的节点通常是短生命周期的,让它们主动上报更轻量。Pull 模式适合那些长跑的服务,比如数据库、消息队列。

实践建议:心跳间隔不要设得太短。1秒一次太频繁,容易把监控系统打爆。我一般设 5-10 秒,配合 3 次失败才判定死亡,这样能过滤掉网络抖动。

2.2 超时机制:给每个操作一个“死线”

心跳检测的是“进程死了”,超时检测的是“操作太慢了”。

流水线里,每个步骤都应该有超时。没有超时的流水线,就像没有刹车的车——你敢开吗?

我曾经踩过一个坑:一个数据清洗步骤,正常跑 30 秒。某天数据量暴增,跑了 20 分钟还没结束。因为没有超时,后面的步骤一直等,结果整个流水线卡了 2 小时。从那以后,我养成了一个习惯——每个步骤必须设超时,而且超时值要留有余量。

超时设置的黄金法则

  • P99 乘以 3:先统计该步骤的 P99 耗时,然后乘以 3 作为超时值。这样能覆盖绝大多数正常波动。
  • 硬超时 + 软超时:软超时触发告警,硬超时直接中断。比如软超时 30 秒,硬超时 60 秒。
  • 超时后要有补偿:超时中断后,不能直接丢弃任务。要记录状态,后续重试或人工介入。

注意:超时值不是越大越好。设得太大,等于没设。设得太小,频繁误杀。我建议先跑一周的基线数据,再定超时值。

2.3 健康检查端点:给流水线做个“体检”

健康检查比心跳更进一步。它不光告诉你“活着没”,还告诉你“健康不健康”。

在流水线里,我通常给每个节点暴露两个端点:/health 和 /ready。

  • /health:存活检查。返回 200 表示进程活着,依赖的基础设施(数据库、缓存)也正常。
  • /ready:就绪检查。返回 200 表示节点已经准备好接收流量或任务。

为什么要分开?因为“活着”和“就绪”是两码事。比如一个节点刚启动,进程活了,但还在加载数据,这时候它不能处理任务。如果只做存活检查,流量打进来就崩了。

最佳实践:健康检查端点要轻量。不要在里面查数据库、调远程接口。否则健康检查本身就成了性能瓶颈。我见过有人把全量数据校验写在健康检查里,结果检查一次要 10 秒——这哪是检查,这是自残。

健康检查的典型实现

# Python Flask 示例
from flask import Flask, jsonify
import redis

app = Flask(__name__)
cache = redis.Redis(host='localhost', port=6379)

@app.route('/health')
def health():
    # 只检查进程和基础依赖
    try:
        cache.ping()
        return jsonify({"status": "healthy"}), 200
    except Exception:
        return jsonify({"status": "unhealthy"}), 503

@app.route('/ready')
def ready():
    # 检查是否加载完配置、连接池是否就绪
    if app.config.get('initialized'):
        return jsonify({"status": "ready"}), 200
    return jsonify({"status": "not ready"}), 503

2.4 日志异常检测:从“蛛丝马迹”中发现故障

前面三种都是主动检测。日志异常检测是被动的——它从日志里挖出问题。

为什么需要它?因为有些故障是渐进的。比如内存泄漏,一开始心跳正常、健康检查正常、超时也没触发。但日志里可能已经出现了 OutOfMemoryError 的警告。等真正挂掉的时候,已经晚了。

我习惯在流水线里做三层日志检测:

  1. 关键字匹配:ERROR、FATAL、Exception、Timeout 等。简单粗暴,但有效。
  2. 频率统计:某个 WARN 日志突然暴增,说明有问题。比如 5 分钟内同一个错误出现了 100 次,就该告警了。
  3. 模式识别:用正则或机器学习识别异常模式。比如“连接失败 -> 重试 -> 连接失败 -> 重试”这种循环,说明网络有问题。

避坑指南:我曾经把日志级别设得太低,DEBUG 日志全量输出。结果一天产生了 200GB 日志,磁盘写满了,流水线直接挂了。后来我学乖了——生产环境只保留 INFO 及以上,DEBUG 日志按需开启。

2.5 四种机制如何协同?

单独用任何一种都有盲区。心跳看不到业务异常,超时管不了慢查询,健康检查可能被绕过,日志检测有延迟。所以,我建议把它们组合起来:

故障检测机制协同架构 流水线节点 心跳检测 超时机制 健康检查 日志异常检测 告警汇聚 & 决策引擎 自动恢复 / 人工介入 四种检测机制各有侧重,组合使用才能覆盖“进程死”、“操作慢”、“业务异常”、“渐进式故障”四种场景 建议:心跳 + 超时作为第一道防线,健康检查作为第二道,日志检测作为兜底

嗯,这里要注意:四种检测的告警要汇聚到一个决策引擎。不要各自为政,否则告警轰炸,运维人员会疯掉。

2.6 实战中的检测参数参考

下面是我在几个项目里用过的参数,供你参考:

检测类型 参数 推荐值 说明
心跳 间隔 / 失败阈值 5s / 3次 内网环境,网络稳定
超时 软超时 / 硬超时 P99×2 / P99×3 需要先跑基线数据
健康检查 检查间隔 / 超时 10s / 2s 检查本身要轻量
日志检测 采样窗口 / 阈值 5min / 10次 避免日志量过大

重要提醒:这些参数不是一成不变的。业务高峰期、大促期间,要动态调整。我习惯把这些参数做成配置中心,运行时可以热更新。