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

wen 实用脚本 78

从入门到生产级监控体系

目录导读

  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、有状态数据库、批处理任务),动态组合上述探测手段,并持续根据误报率调优参数,监控是一种投资,而你的脚本是收益率最高的那一笔。

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