从入门到生产级监控体系
目录导读
- 为什么需要服务存活检测脚本? —— 监控的“第一道防线”
- 探测手段选型 —— TCP、HTTP、进程、自定义协议如何抉择
- 核心代码逻辑拆解 —— 状态机、超时处理与重试策略
- 生产环境必踩的坑 —— 僵尸进程、半开连接与误报抑制
- 脚本与监控系统集成 —— Prometheus、Nagios、Zabbix 对接范式
- 常见问答(FAQ) —— 高频疑问精解
为什么需要服务存活检测脚本?
在分布式系统与微服务架构普及的今天,单个服务实例宕机不再是“小故障”,而是可能引发雪崩效应的导火索,我们通常通过三层探测体系来判断服务是否健康:

| 层级 | 检测方式 | 响应速度 | 适用场景 |
|---|---|---|---|
| L1 进程级 | ps -ef 或 systemctl status |
毫秒级 | 检测进程是否被OOM Kill |
| L2 端口级 | TCP connect / 半开扫描 | 秒级 | 服务监听端口是否可达 |
| L3 业务级 | HTTP GET / 自定义心跳 | 秒~分钟级 | 接口是否返回200且业务数据正确 |
核心结论:存活检测脚本的本质是“在正确的时间,用正确的协议,做一次判定”,而不是简单地检查进程存在与否。
探测手段选型:不止是 ping 这么简单
TCP 端口探测(最常用)
import socket
def check_tcp(host, port, timeout=3):
try:
with socket.create_connection((host, port), timeout=timeout):
return True
except OSError:
return False
关键点:仅做connect,不发送任何业务数据,检测“端口是否监听”。
HTTP 业务探测(生产首选)
curl -s -o /dev/null -w "%{http_code}" -m 5 http://localhost:8080/healthz
进阶技巧:应同时校验状态码、响应时间、关键词,例如返回 {"status":"UP"} 才算健康。
进程/系统资源检测
pgrep -f "java.*app.jar" || exit 1 # 精确匹配进程名 # 要注意:进程存在 ≠ 服务可用,需配合端口探测
自定义协议(RPC / gRPC)
对于 Dubbo、gRPC 等框架,需使用对应的健康检查接口,gRPC 的 grpc_health_probe 工具。
核心代码逻辑拆解:状态机设计
一个健壮的检测脚本必须包含三态判定与去抖机制:
状态机:UNKNOWN → (探测失败) → FAILED → (连续N次失败) → ALERT
↘ (探测成功) → PASS
伪代码示例(生产级逻辑):
#!/bin/bash
RETRY=3 # 重试次数
INTERVAL=2 # 重试间隔(秒)
CONSEC_FAIL=0 # 连续失败计数器
check_once() {
if curl -sf -m 5 http://localhost:8080/healthz; then
CONSEC_FAIL=0
echo "PASS - $(date)" >> /var/log/health.log
else
((CONSEC_FAIL++))
echo "FAIL - attempt $CONSEC_FAIL" >> /var/log/health.log
fi
}
while [ $CONSEC_FAIL -lt $RETRY ]; do
check_once
[ $CONSEC_FAIL -eq 0 ] && break
sleep $INTERVAL
done
[ $CONSEC_FAIL -ge $RETRY ] && exit 1 # 触发告警
exit 0
解读:连续失败3次(每次间隔2秒)才判定为“服务不可用”,有效过滤瞬断、GC暂停、网络抖动导致的误报。
生产环境必踩的坑:别让你的监控“吵死人”
-
半开连接与 TIME_WAIT 积累
使用netstat或ss检查端口时,不要只统计LISTEN状态,还要关注连接队列溢出(ListenOverflows),建议使用ss -lnt检查。 -
僵尸进程陷阱
pgrep -f可能匹配到残留的孤儿进程,但实际服务端口已关闭。必须组合端口探测,而非单看进程。 -
超时设置过短或过长
- 超时过短(<100ms):网络正常也会误报
- 超时过长(>10s):检测脚本本身会堆积成故障点
经验值:内网探测 2~3 秒,公网探测 5 秒。
-
脚本自身引用的配置依赖
若脚本依赖数据库或配置文件,需确保这些资源独立于被监控服务,否则会连锁误报。 -
防重入锁
如果监控系统每 30 秒调用一次脚本,而脚本本身阻塞超过 30 秒,会引发脚本并发执行,务必加入文件锁:exec 9>/var/lock/health.lock flock -n 9 || exit 1 # 无法获取锁则直接退出
脚本与监控系统集成:告别裸奔式告警
对接 Prometheus(推荐使用 exporter)
# prometheus.yml 中的静态配置
- job_name: 'custom_health'
static_configs:
- targets: ['10.0.0.5:9110']
metrics_path: '/probe'
params:
module: [health_check]
脚本只需输出 0 或 1 到 node_exporter 的 textfile 目录:
echo "service_health_status 1" > /var/lib/node_exporter/textfile/health.prom
对接 Zabbix / Nagios
- Nagios 插件规范:输出
OK | WARNING | CRITICAL文本,并附带性能数据 - Zabbix 自定义 Key:
UserParameter=service.check,/opt/script/health_check.sh $1
常见问答(FAQ)
Q1:HTTP 检测返回 404 算存活吗?
不一定,404 是业务上的“资源不存在”,但入口本身是活的;但如果 404 是负载均衡的路由失效,则视为故障。建议配置一个专门的 /healthz 端点,只返回 200 或非 200。
Q2:检测脚本本身挂了怎么办?
这是“监控的监控”问题,标准解法:
- 由独立进程(如 systemd timer)定期检查脚本文件是否可执行
- 将脚本嵌入到容器 init 进程中,Docker 的
HEALTHCHECK指令HEALTHCHECK --interval=30s --timeout=3s --retries=3 \ CMD curl -f http://localhost/healthz || exit 1
Q3:对 HTTPS 加密接口检测要额外做什么?
需要处理证书校验问题:
curl -k -sf https://example.com/healthz # -k 跳过证书验证,视安全性而定
建议将自签证书添加到系统信任链,而不要盲目 -k。
Q4:如何检测 UDP 服务存活?
UDP 无连接,常规 connect 方法失效,可发送一个自定义心跳包并期望固定响应,或者使用 nc -u -z -w 3 host port 测试端口是否通。
Q5:多实例场景下,如何避免告警风暴?
在检测脚本中加入 “集群仲裁”逻辑:3 个节点中至少有 2 个存活才视为集群健康,否则只告警不调用隔离接口(如 Redis 的主从切换)。
服务存活检测脚本的本质是“衡量的艺术”——你不能用一根温度计测量整片海洋,请根据你的业务特征(无状态 API、有状态数据库、批处理任务),动态组合上述探测手段,并持续根据误报率调优参数,监控是一种投资,而你的脚本是收益率最高的那一笔。