用 curl + jq 给接口做一次靠谱的健康检查
前几天半夜被报警吵醒,服务其实还活着,只是某个下游接口超时,整个页面跟着变慢。那次之后,我给常驻服务加了一套固定的健康检查做法:先分清接口是不是真的健康,再用脚本定期去探,最后把结果记下来,方便半夜排查时一眼看清问题在哪一环。
先说一个容易踩的坑:只看进程在不在,或者只看端口通不通,并不能说明服务可用。进程活着、端口监听着,但数据库连不上、缓存挂了、关键依赖超时,接口照样返回错误。所以健康检查要查到业务能真正跑通的那一层,至少要区分存活和就绪。
我常用的做法是给服务留两个端点。一个是存活端点,例如 GET /health,只回答进程本身是否还在工作,返回越轻越好,不查数据库、不做重计算。另一个是就绪端点,例如 GET /ready,在里面顺带检查数据库、缓存等关键依赖,依赖不通就明确返回不就绪,负载均衡就不会再把新流量分给它。两个端点分开,重启和摘流量就不会互相干扰。
有了端点,接下来就是用 curl 去探。最简单的检查只看状态码,不看正文:
curl -s -o /dev/null -w "%{http_code}" --max-time 3 http://127.0.0.1:8080/health
echo
这里有几个细节值得注意。-s 是安静模式,不输出进度;-o /dev/null 是丢掉正文,只关心状态;-w 输出状态码;–max-time 3 是给检查定一个上限,超过 3 秒没响应就按失败处理。没有超时限制的检查最危险,检查脚本自己先被拖住,后面一串检查全都会排队。
状态码能说明通不通,但说不清是哪一部分出了问题。这时候就该看正文,再用 jq 把字段取出来:
curl -s --max-time 3 http://127.0.0.1:8080/health | jq '{status: .status, db: .components.db.status, redis: .components.redis.status}'
如果返回里有 status、db、redis 这类字段,jq 一行就能把关键信息抽出来。比起把一整段 JSON 打印到日志里,这种方式在报警信息里也更干净。检查脚本里我一般还会把状态码和解析结果分开处理:先判断 HTTP 状态码是不是 200,再判断 jq 取到的 status 是不是 UP,两个都通过才算健康。
把一次检查写成脚本后,就可以交给定时任务定期跑。下面是一个可以直接改地址复用的小脚本思路:
#!/bin/bash
set -u
URL="http://127.0.0.1:8080/health"
CODE=$(curl -s -o /tmp/health.json -w "%{http_code}" --max-time 3 "$URL")
if [ "$CODE" != "200" ]; then
echo "$(date '+%F %T') health check failed, http_code=$CODE" >> /var/log/health-check.log
exit 1
fi
STATUS=$(jq -r '.status // empty' /tmp/health.json)
if [ "$STATUS" != "UP" ]; then
echo "$(date '+%F %T') health check failed, status=$STATUS" >> /var/log/health-check.log
exit 1
fi
echo "$(date '+%F %T') ok" >> /var/log/health-check.log
这个脚本只做三件事:取状态码、取状态字段、把结果写进日志。失败时日志里有时间和失败原因,成功时也留一条记录,方便事后对照报警时间。生产上不要把检查脚本写得太复杂,检查本身越重,越容易成为新的故障点。
最后说一下排查时的顺序,我现在基本固定下来:先看健康检查最近几次的结果,确认是偶发超时还是持续失败;再看 /ready 返回的依赖状态,定位到数据库还是缓存;最后才去翻应用日志。这样做的好处是,先用一个很轻的检查把范围缩小,再去翻重的日志,而不是一上来就在海量日志里大海捞针。
总结一下,靠谱的健康检查并不复杂:存活和就绪分开、检查一定要设超时、状态码和正文字段都要看、结果落到日志里。把这四点固定下来,下次半夜收到报警,至少能先知道服务是真挂了,还是某个依赖拖住了它。
- 点赞
- 收藏
- 关注作者
评论(0)