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 的警告。等真正挂掉的时候,已经晚了。
我习惯在流水线里做三层日志检测:
- 关键字匹配:ERROR、FATAL、Exception、Timeout 等。简单粗暴,但有效。
- 频率统计:某个 WARN 日志突然暴增,说明有问题。比如 5 分钟内同一个错误出现了 100 次,就该告警了。
- 模式识别:用正则或机器学习识别异常模式。比如“连接失败 -> 重试 -> 连接失败 -> 重试”这种循环,说明网络有问题。
避坑指南:我曾经把日志级别设得太低,DEBUG 日志全量输出。结果一天产生了 200GB 日志,磁盘写满了,流水线直接挂了。后来我学乖了——生产环境只保留 INFO 及以上,DEBUG 日志按需开启。
2.5 四种机制如何协同?
单独用任何一种都有盲区。心跳看不到业务异常,超时管不了慢查询,健康检查可能被绕过,日志检测有延迟。所以,我建议把它们组合起来:
嗯,这里要注意:四种检测的告警要汇聚到一个决策引擎。不要各自为政,否则告警轰炸,运维人员会疯掉。
2.6 实战中的检测参数参考
下面是我在几个项目里用过的参数,供你参考:
| 检测类型 | 参数 | 推荐值 | 说明 |
|---|---|---|---|
| 心跳 | 间隔 / 失败阈值 | 5s / 3次 | 内网环境,网络稳定 |
| 超时 | 软超时 / 硬超时 | P99×2 / P99×3 | 需要先跑基线数据 |
| 健康检查 | 检查间隔 / 超时 | 10s / 2s | 检查本身要轻量 |
| 日志检测 | 采样窗口 / 阈值 | 5min / 10次 | 避免日志量过大 |
重要提醒:这些参数不是一成不变的。业务高峰期、大促期间,要动态调整。我习惯把这些参数做成配置中心,运行时可以热更新。