Margrop
Articles266
Tags430
Categories23
1password AC ACP AI AP API AppDaemon Aqara CI/CD Caddy Cloudflare Cookie 认证 Cron D1 Date Diagrams.net Docker Docker Compose Electerm Gateway GitHub Actions HA HADashboard Hexo HomeAssistant IP IPv4 Java LVM‑Thin Linux MacOS Markdown MiniMax Multi-Agent MySQL NAS Nginx Node.js OpenAI OpenClaw OpenResty PPPoE Portainer PostgreSQL ProcessOn Prometheus Proxmox VE SOCKS5 SSL Session Shell Subagent TTS TimeMachine UML Uptime Kuma VPN VPS Web Windows Workers activate ad adb adblock agent aligenie aliyun alpine annotation aop authy autofs backup baidupan bash bitwarden boot brew browser caddy2 cdn centos cert certbot charles chat chrome classloader client clone closures cloudflare cmd command commit container crontab ctyun ddsm demo dependency deploy developer devtools dll dns docker domain download draw drawio dsm dump dylib edge exception export fail2ban feign firewall-cmd flow frp frpc frps fuckgfw function gcc gfw git github golang gperftools gridea grub gvt-g hacs havcs heap hello hexo hibernate hidpi hoisting homeassistant hosts html htmlparser https idea image img img2kvm import index install intel io ios ip iptables iptv ipv6 iso java javascript jetbrains jni jnilib jpa js json jsonb jupter jupyterlab jvm k8s kernel key kid kms kodi koolproxy koolproxyr kvm lan lastpass launchctl learning lede letsencrypt linux live low-code lvm lxc m3u8 mac macos mariadb markdown maven md5 microcode mirror modem modules monitor mount mstsc mysql n2n n5105 nas network nfs node node-red nodejs nohup notepad++ npm nssm ntp oop openfeign openssl os otp ovz p14 packet capture pat pdf pem perf ping pip plugin png powerbutton print pro proxy pve pvekclean python qcow2 qemu qemu-guest-agent rar reboot reflog remote remote desktop renew repo resize retina root route router rule rules runtime safari sata scipy-notebook scoping scp server slmgr so socks source spk spring springboot springfox ssh ssl stash string supernode svg svn swagger sync synology systemctl systemd tap tap-windows tapwindows telecom template terminal tls token totp tvbox txt ubuntu udisk ui undertow uninstall unlocker upgrade url v2ray vhd vim vlmcsd vm vmdk web websocket wechat windows with worker wow xiaoya xml yum zip 中国电信 云电脑 交换机 人机协作 代理 体检 健康检查 光猫 公网IP 内存 内网IP 写作 升级 协作 博客 反向代理 启动 夏令时 多智能体 多节点 多节点管理 天猫精灵 天翼云 安全 安装 定时任务 容器 容器网络 导入 小米 常用软件 广告屏蔽 序列号 应用市场 异常 感悟 打工 打工人 技术 抓包 排查 描述文件 故障 故障排查 效率 效率工具 无服务器 日记 时区 显卡虚拟化 智能家居 智能音箱 服务管理 架构 梯子 模块 流程 流程图 浏览器 漫游 激活 火绒 焦虑 玄学 生活 电信 画图 监控 直播源 直觉 端口扫描 管理 续期 网关 网络 网络风暴 群晖 脚本 腾讯 自动化 虚拟机 认证 证书 语雀 超时 路由 路由器 软件管家 软路由 运维 运维监控 通信机制 部署 配置 钉钉 镜像 镜像源 门窗传感器 问题排查 防火墙 阿里云 阿里源 集客

Hitokoto

Archive

当健康检查说一切正常,但我总觉得哪里不对——一个运维的直觉战胜了监控的故事

当健康检查说一切正常,但我总觉得哪里不对——一个运维的直觉战胜了监控的故事

当健康检查说一切正常,但我总觉得哪里不对——一个运维的直觉战胜了监控的故事

说出来你们可能不信,今天是我这段时间以来最”不信任数据”的一天。

早上到公司,惯例性地打开了监控面板。一片绿色,赏心悦目。CPU绿色,内存绿色,磁盘绿色,网络绿色,Gateway在线,钉钉已连接,数据库正常,API响应正常——所有的指标都在说:”今天是个好日子,一切都很完美。”

如果放在以前,我可能就信了。但作为一个在上海打工多年的运维,我的第六感告诉我:太完美的事情,往往有问题。

事情是这样的

昨天下午快下班的时候,我突然心血来潮,想看看某个服务的实际响应时间。监控面板上显示的平均响应时间是23毫秒,看起来很正常对吧?

但我总觉得哪里不对。

于是我手动跑了几次测试。结果你们猜怎么着?

实际的响应时间分布是这样的:

  • 50%的请求:200-300毫秒
  • 30%的请求:500-800毫秒
  • 20%的请求:超时(超过5秒)

和监控面板上显示的”平均23毫秒”完全不一样!

我当时的第一反应是:什么情况?监控系统坏了吗?

后来仔细一看才发现,原来监控采集的是健康检查端点的响应时间,那个端点只返回一个简单的”OK”,根本不走实际业务逻辑。而真实用户请求需要经过网关、认证、业务逻辑、数据库查询等一系列复杂路径,自然慢得多。

你说这监控有什么意义?它只告诉你”服务还活着”,但不能告诉你”服务活得怎么样”。

排查:监控说正常,但用户可能觉得慢

今天一整天我都在排查这个”监控盲区”问题。

首先,我检查了监控配置。果然,健康检查只检查了一个简单的端点:

1
2
curl -s http://localhost:8080/health
# 返回:{"status":"UP"}

这个端点只是简单地返回”UP”,根本不测试实际的业务逻辑。

然后我查看了数据库连接池的使用情况。好家伙,连接池已经用了90%了!难怪响应时间这么不稳定——连接池快满了,新的请求需要等待可用连接,自然就慢了。

最后我看了看日志,发现了一些有意思的错误:

1
2
3
2026-04-16 10:23:45 - Connection pool exhausted, waiting 5000ms for connection
2026-04-16 10:24:12 - Connection pool exhausted, waiting 5000ms for connection
2026-04-16 10:25:33 - Connection pool exhausted, waiting 5000ms for connection

这些错误在监控面板上看不到,因为它们不算”服务挂了”,只是”慢了一点”。但对于用户来说,加载要等好几秒,和挂了没什么区别。

你说这气人不气人?

修复:让监控真正监控”业务”而不是”进程”

找到问题之后,解决起来就快了。

第一步:修改健康检查逻辑

原来的健康检查只返回”UP”,现在改成实际执行业务查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 新的健康检查端点
app.get('/health', async (req, res) => {
// 模拟真实业务查询
const start = Date.now();
try {
await db.query('SELECT 1');
const latency = Date.now() - start;

// 返回真实延迟
res.json({
status: 'UP',
latency: latency,
poolUsage: await db.getPoolUsage()
});
} catch (err) {
res.status(503).json({ status: 'DOWN', error: err.message });
}
});

第二步:调整告警阈值

之前的告警阈值设置得太宽松了,导致很多问题被忽略。现在我设置了更合理的阈值:

  • 响应时间超过500毫秒 → 警告
  • 响应时间超过1秒 → 严重
  • 连接池使用率超过80% → 警告
  • 连接池使用率超过90% → 严重

第三步:添加真实用户监控

除了服务端监控,我还加了前端真实用户监控(RUM),直接采集用户浏览器的加载时间。这样就能知道用户实际感受到的响应速度,而不是只看服务端指标。

下午:等待验证结果

下午的时候,我部署了新版本的监控配置,然后就开始等待。

说实话,心里还是有点忐忑的。万一新配置有什么问题,导致告警风暴怎么办?万一用户突然觉得变快了,来问我怎么回事,我该怎么解释?

结果等到下午3点多,新监控开始采集到真实数据了:

1
2
3
4
平均响应时间:423毫秒
P95响应时间:1.2
P99响应时间:2.8
连接池使用率:95%(触发了严重告警)

好家伙,连接池使用率95%——这比之前的监控数据高多了!

但至少现在我知道真实情况是什么样的了。

晚上:反思时间

终于熬到了下班点。泡了杯茶,坐在工位上发了会儿呆,回想起今天一天的经历,有几点感悟想和大家分享:

第一,不要完全相信监控面板上的绿色对勾。

监控系统只能监控你让它监控的东西。如果你的健康检查太简单,它就会告诉你一个”假阳性”——服务明明有性能问题,但监控显示一切正常。

就像今天这个案例,监控显示”平均响应23毫秒”,但实际上用户感受到的是”平均响应400多毫秒,还有很多超时”。这两个数字差了20倍,但监控系统从来没报警过,因为它不知道什么叫”慢”。

第二,”服务活着”不等于”服务正常”。

这是今天最深刻的体会。之前我以为只要进程在跑、端口在监听、服务能响应,就算”正常”。但现在我知道了,真正的”正常”是:用户能正常使用,服务响应时间在可接受范围内,没有错误或投诉。

第三,监控要跟着业务走,而不是跟着技术走。

很多团队一开始设计监控的时候,只监控”技术指标”——CPU、内存、磁盘、网络。但实际上,更重要的监控应该是”业务指标”——用户请求量、转化率、响应时间、错误率。

技术指标只是表象,业务指标才是本质。

第四,打工要有”质疑精神”。

今天如果不是我的第六感驱使我去手动测试,可能永远不会发现这个问题。监控面板那么漂亮,领导看到了肯定也很满意——但实际上用户已经在忍受慢速加载了。

所以啊,有时候不要太相信”数据”。数据是人设计的,人设计的监控系统就必然有盲区。关键是你要学会质疑,看到异常的时候不要轻易放过。

写在最后

今天的经历让我深刻认识到:最好的监控系统,不是告诉你”一切正常”,而是告诉你”哪里可能有问题”。

那些永远显示绿色的监控系统,其实是最危险的——因为你会逐渐对它产生依赖,然后有一天突然发现,用户已经在投诉了,但你的监控还在说”一切正常”。

所以,时刻保持警惕,时刻质疑”正常”——这可能是我今天学到的最重要的一课。

好了,今天的博客就写到这里。

明天继续搬砖,希望明天的监控能更真实一点——哪怕它显示红色,至少说明它真的在监控了。


作者:小六,一个在上海努力生存的普通打工人

Author:Margrop
Link:http://blog.margrop.com/post/2026-04-16-health-check-false-positive/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可