如何编写服务存活检测脚本

wen 实用脚本 4

从入门到生产级监控体系

目录导读

  1. 为什么需要服务存活检测脚本? —— 监控的“第一道防线”
  2. 探测手段选型 —— TCP、HTTP、进程、自定义协议如何抉择
  3. 核心代码逻辑拆解 —— 状态机、超时处理与重试策略
  4. 生产环境必踩的坑 —— 僵尸进程、半开连接与误报抑制
  5. 脚本与监控系统集成 —— Prometheus、Nagios、Zabbix 对接范式
  6. 常见问答(FAQ) —— 高频疑问精解

为什么需要服务存活检测脚本?

在分布式系统与微服务架构普及的今天,单个服务实例宕机不再是“小故障”,而是可能引发雪崩效应的导火索,我们通常通过三层探测体系来判断服务是否健康:

如何编写服务存活检测脚本

层级 检测方式 响应速度 适用场景
L1 进程级 ps -efsystemctl 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暂停、网络抖动导致的误报。


生产环境必踩的坑:别让你的监控“吵死人”

  1. 半开连接与 TIME_WAIT 积累
    使用 netstatss 检查端口时,不要只统计 LISTEN 状态,还要关注连接队列溢出(ListenOverflows),建议使用 ss -lnt 检查。

  2. 僵尸进程陷阱
    pgrep -f 可能匹配到残留的孤儿进程,但实际服务端口已关闭。必须组合端口探测,而非单看进程。

  3. 超时设置过短或过长

    • 超时过短(<100ms):网络正常也会误报
    • 超时过长(>10s):检测脚本本身会堆积成故障点
      经验值:内网探测 2~3 秒,公网探测 5 秒。
  4. 脚本自身引用的配置依赖
    若脚本依赖数据库或配置文件,需确保这些资源独立于被监控服务,否则会连锁误报。

  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]

脚本只需输出 01node_exporter 的 textfile 目录:

echo "service_health_status 1" > /var/lib/node_exporter/textfile/health.prom

对接 Zabbix / Nagios

  • Nagios 插件规范:输出 OK | WARNING | CRITICAL 文本,并附带性能数据
  • Zabbix 自定义 KeyUserParameter=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、有状态数据库、批处理任务),动态组合上述探测手段,并持续根据误报率调优参数,监控是一种投资,而你的脚本是收益率最高的那一笔。

抱歉,评论功能暂时关闭!