服务器监控需要关注哪些指标?CPU、内存、磁盘、网络与响应时间

装完监控之后会面临一个更实际的问题:指标有几百上千个,到底该看哪几个?如果把所有指标都设成告警,结果必然是每天收到几百条通知,然后所有人对告警视而不见——这比完全没有监控更糟,因为它给了你一种虚假的安全感。

这篇文章给出一份经过筛选的清单:四类系统资源各看哪几个指标(而不是全部)、应用层必须有的三个指标、RED 与 USE 怎么落地成具体面板、以及最关键的——阈值该怎么定才能既不漏报又不吵。最后讨论一个容易被忽略的理念问题:监控显示的是症状,不是原因。

文中的每个指标都会给出"为什么重要"和"看的时候要注意什么",而不是罗列一大堆名字让你自己挑。

一、筛选指标的原则

在展开清单之前,先确立三个筛选原则,它们能帮你面对任意新指标时做出判断。

原则一:能触发行动的才报警,只能提供上下文的只做展示。 CPU 使用率 85% 且持续 10 分钟可能需要处理;而"某个 exporter 的 scrape 耗时"通常只需要在排查问题时看。把后者设成告警就是噪音。

原则二:优先选择饱和类指标而不是使用率类指标。 使用率高是"忙",饱和是"排队"。排队会直接导致延迟劣化,是更早、更准的信号。典型例子:CPU 的运行队列长度比使用率更早反映问题,磁盘的 await 比 %util 更有判断力。

原则三:每个指标都要知道"异常时下一步做什么"。 如果一个指标异常了但你不知道该查什么,那它暂时不该进告警列表。

还有一个数量上的经验:一套覆盖几十台机器的监控系统,核心告警控制在 20 条以内。超过这个数量,值班人员不可能每条都认真处理。

二、CPU:四个指标,不止使用率

指标 来源 为什么看 注意点
CPU 使用率(分项) top / node_exporter 基础容量信号 必须拆成 us/sy/wa/st 看,总数没意义
Load Average /proc/loadavg 反映排队程度 必须除以逻辑核数判断
运行队列长度 vmstat 的 r 列 瞬时排队,比 Load 更敏感 Load 是平均值会掩盖毛刺
上下文切换率 vmstat 的 cs 列 发现"没干活但在切换"的浪费 持续数万以上需要关注

CPU 使用率的四个分项各有不同含义,分开看才能定位:us 高说明应用在做计算、sy 高说明系统调用或切换太多、wa 高说明卡在 IO(这时候加 CPU 没用)、st 高说明虚拟机被宿主机抢走了 CPU(guest 内无解)。

相关 PromQL:

# 总使用率
100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# 分项使用率(user / system / iowait / steal)
avg by(instance)(rate(node_cpu_seconds_total{mode="user"}[5m])) * 100
avg by(instance)(rate(node_cpu_seconds_total{mode="system"}[5m])) * 100
avg by(instance)(rate(node_cpu_seconds_total{mode="iowait"}[5m])) * 100
avg by(instance)(rate(node_cpu_seconds_total{mode="steal"}[5m])) * 100

# 每核归一化后的 Load(按CPU 核数归一化的 1 分钟负载)
node_load1 / count by(instance)(node_cpu_seconds_total{mode="idle"})

# 上下文切换速率
rate(node_context_switches_total[5m])

告警建议:

  • 使用率(排除 idle)> 85% 持续 10 分钟 → warning
  • Load/核数 > 3 持续 10 分钟 → warning
  • wa > 20% 持续 5 分钟 → warning,但告警内容应该指向"去查磁盘 IO"而不是"去查 CPU"
  • st > 10% 持续 10 分钟 → warning,内容是"宿主机超卖,考虑迁移"

不要设"CPU 使用率 > 60% 就告警"。 这个阈值会导致每天几百条噪音,因为任何批处理、备份、日志压缩都会瞬时冲到 60%。

三、内存:可用量远比使用率重要

指标 来源 为什么看 注意点
MemAvailable /proc/meminfo / node_exporter 最关键的单一指标 已包含可回收缓存
swap 使用量与趋势 free 增长趋势比绝对值重要 少量使用不一定有问题
si / so(换页速率) vmstat / node_exporter 持续非 0 就是问题 这是最灵敏的内存压力信号
OOM 事件 dmesg / journal 已造成伤害的事后信号 需要日志采集才能发现
Slab(SUnreclaim) /proc/meminfo 发现内核态泄漏 增长异常时才看
# 可用内存百分比
100 * node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes

# swap 使用百分比
100 * (1 - node_memory_SwapFree_bytes / node_memory_SwapTotal_bytes)

# 换入换出速率(KB/s)
rate(node_vmstat_pswpin[5m]) * 4      # 页面数 × 4KB
rate(node_vmstat_pswpout[5m]) * 4

# 主要缺页率(需要磁盘访问的缺页,内存不足的典型表现)
rate(node_vmstat_pgmajfault[5m])

告警建议:

  • 可用内存百分比 < 10% 持续 5 分钟 → warning
  • 可用内存 < 5% 持续 2 分钟 → critical
  • swap 使用率 > 20% 且 1 小时内增长 > 5% → warning(用增长率而不是绝对值)
  • pswpin/pswpout 持续 > 0 超过 5 分钟 → warning
  • 检测到 "Out of memory" 内核日志 → critical

为什么用趋势而不是绝对值判断 swap? 因为很多内核会在系统空闲时把很少使用的匿名页提前换出,以腾出更多内存给文件缓存,这是正常的内存管理行为,swap 占用可能有 10~20% 但系统完全健康。真正危险的是"持续增长不回落"。

四、磁盘:空间、IOPS、await 三个维度

磁盘要分成两件事监控:容量和性能,它们完全独立,经常被混为一谈。

容量维度

指标 为什么看 告警建议
空间使用率 满了会导致写失败 > 80% warning,> 90% critical
inode 使用率 空间没满但 inode 耗尽同样无法创建文件 > 80% warning
剩余天数预测 给出行动窗口,比静态阈值好 < 4 小时 critical
# inode 使用率(最容易漏的一项)
100 - (100 * node_filesystem_files_free{mountpoint="/"} / node_filesystem_files{mountpoint="/"})

# 4 小时后是否会被写满(线性预测)
predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfs|squashfs"}[6h], 4*3600) < 0

# 文件系统只读检测(文件系统进入只读往往是磁盘故障前兆)
node_filesystem_readonly == 1

inode 耗尽极具迷惑性:df -h 显示还剩 40% 空间,但应用报 "No space left on device"。海量小文件场景(邮件队列、PHP session 文件、容器镜像层、日志切割碎片)经常遇到。排查用 df -i。

性能维度

指标 含义 注意点
await(r_await/w_await) 请求平均等待时间 最可靠判断依据,需与自家基线对比
IOPS(r/s + w/s) 每秒请求数 对比设备规格
吞吐(rkB/s + wkB/s) 每秒字节数 对比带宽上限
avgqu-sz 平均队列长度 持续 > 1 说明排队
%util 设备忙碌占比 SSD 上参考价值下降
# 磁盘忙碌程度(等价于 iostat 的 %util)
rate(node_disk_io_time_seconds_total[5m]) * 100

# 平均请求等待时间(await,单位秒)
rate(node_disk_read_time_seconds_total[5m]) / rate(node_disk_reads_completed_total[5m])
rate(node_disk_write_time_seconds_total[5m]) / rate(node_disk_writes_completed_total[5m])

# IOPS
rate(node_disk_reads_completed_total[5m]) + rate(node_disk_writes_completed_total[5m])

# 吞吐
rate(node_disk_read_bytes_total[5m]) + rate(node_disk_written_bytes_total[5m])

告警建议:

  • 磁盘 4 小时内将写满 → critical(这条优先级最高)
  • await > 基线 2 倍持续 5 分钟 → warning
  • IOPS 达到设备规格 80% → warning
  • SMART 错误计数增长 → critical(磁盘故障前的信号)

五、网络:包量常被忽略,重传最重要

指标 为什么看 参考阈值
带宽(rxkB/s、txkB/s) 基础容量 接近网卡规格 70% 关注
包速率(rxpck/s) 小包洪水时带宽不高但 CPU 已吃紧 单机几十万 pps 需关注
重传率 TCP 重传会触发拥塞控制降窗,吞吐骤降 超过 0.1% 就值得查
丢包与错误计数 硬件或驱动问题 出现增长即排查
连接数与 TCP 状态分布 TIME_WAIT 过多可能是端口耗尽前兆 结合业务判断
# 出入带宽
rate(node_network_receive_bytes_total{device!~"lo|veth.*|docker.*"}[5m]) * 8   # 比特/秒
rate(node_network_transmit_bytes_total{device!~"lo|veth.*|docker.*"}[5m]) * 8

# 包速率
rate(node_network_receive_packets_total{device!~"lo|veth.*"}[5m])

# 错误包与丢包速率
rate(node_network_receive_errs_total[5m]) + rate(node_network_transmit_errs_total[5m])
rate(node_network_receive_drop_total[5m])

# TCP 重传速率(千分之一的重传就足以让吞吐明显下降)
rate(node_netstat_TcpRetransSegs[5m])

# 处于 TIME_WAIT 的连接数(需要 tcpstat collector)
node_tcpstat_CurrEstab

用命令行快速核查:

sar -n DEV 1              # rxkB/s、txkB/s、rxpck/s、txpck/s
sar -n ETCP,TCP 1         # active/s、passive/s、retrans/s、isegerr/s
ip -s link                # RX/TX 的 errors、dropped、overrun
ethtool -S eth0 | grep -iE 'drop|err|over|miss'    # 网卡层的丢包与 Ring Buffer 溢出

丢包定位的顺序很重要:先看网卡统计(是否是 Ring Buffer 溢出或物理问题),再看内核 netdev_max_backlog 是否打满(ip -s link 的 dropped 或 /proc/net/softnet_stat 的相应列),最后看是否被防火墙丢弃(iptables/nftables 的规则计数)。

cat /proc/net/softnet_stat           # 第二列是被丢弃的包数(每 CPU 一行)
nft list ruleset -a 2>/dev/null | head -40      # 查看 nftables 规则计数

六、应用层:三个必看指标

系统级指标只能告诉你"机器好不好",不能告诉你"服务好不好"。应用层至少要采集这三项:

指标 含义 为什么必看
QPS / TPS 每秒请求数或事务数 流量异常(暴跌或暴增)本身就是最强信号
响应时间 P95 / P99 分位数而非平均值 平均值会被长尾掩盖,用户抱怨的永远是长尾
错误率 失败请求占比 最直接的质量信号

为什么必须看 P95/P99 而不是平均值? 假设 100 个请求里有 95 个是 20ms、5 个是 3000ms,平均值是 169ms,看起来"还行"。但那 5 个用户体验的是 3 秒的白屏。平均值会系统性地掩盖长尾问题。P99 的含义是"99% 的请求比这个值快",直接反映最差那 1% 的体验。

配套还应该看的:

  • 超时率:与外部依赖相关
  • 连接池使用率:连接耗尽会导致大量请求排队,表现往往是延迟升高而非错误
  • 队列积压:消息堆积、线程池队列长度
  • 依赖的下游响应时间:判断是不是别人的问题

Nginx 侧可以直接从日志里算这些:

# 假设日志格式里带了 $request_time 和 $upstream_response_time
# Nginx 配置:
log_format main '... "$request" $status $body_bytes_sent ... $request_time $upstream_response_time';
access_log /var/log/nginx/access.log main buffer=32k flush=5s;

# 快速统计 P95 响应时间与错误率
awk '{print $NF}' /var/log/nginx/access.log | sort -n > /tmp/rt.txt
awk 'BEGIN{c=0} {a[c++]=$1} END{printf "count=%d p50=%.3f p95=%.3f p99=%.3f\n", c, a[int(c*0.5)], a[int(c*0.95)], a[int(c*0.99)]}' /tmp/rt.txt

# 状态码分布
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

# 实时 QPS(统计近 1 分钟请求数)
tail -f /var/log/nginx/access.log | pv -l -i 60 -r > /dev/null

生产环境更推荐把这些做成 exporter(如 mtail、promtail 提取日志中的指标,或直接在应用里用 Prometheus client 暴露 /metrics),而不是每次都去 awk 一遍。

用 Prometheus 表达应用层指标

# QPS(假设有 http_requests_total Counter)
sum(rate(http_requests_total[1m]))

# 错误率
100 * sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))

# P95 响应时间(Histogram 类型)
histogram_quantile(0.95, sum by(le)(rate(http_request_duration_seconds_bucket[5m])))

# P99
histogram_quantile(0.99, sum by(le)(rate(http_request_duration_seconds_bucket[5m])))

七、RED 与 USE 落地成面板

前面列的指标体系,其实可以用两个框架组织起来,这样不会遗漏也不会重复。

USE(资源视角):给每个资源排三列——使用率、饱和度、错误。

资源 U S E
CPU 分项使用率 Load/核、运行队列、上下文切换 —
内存 可用量(反向) swap 进出速率、缺页率 OOM 事件
磁盘 忙碌百分比、IOPS await、队列长度、积压请求 介质错误、只读挂载
网络 带宽占用率 重传率、丢包、队列丢包 网卡错误计数

RED(服务视角):速率(Rate)、错误(Errors)、耗时(Duration)。对应上面的 QPS、错误率、P95/P99。

在实际的 Grafana 面板布局中,建议采用这样的梯度:

第一行:核心健康度(RED 三项)—— 一眼知道服务好不好用
第二行:资源总览(USE 四资源的使用率)—— 知道机器是否吃紧
第三行:饱和度细节(Load、await、swap 进出、重传)—— 定位方向
第四行:分项下钻(CPU 分项、per-disk、per-interface)—— 找到具体部件

这个梯度遵循的是"从结论到细节":先看有没有问题(第一行),再看哪类资源(第二行),再看严重程度(第三行),最后才是具体是谁(第四行)。 把细节堆在第一屏会导致故障时找不到重点。

八、阈值怎么定:避免固定阈值

这是本文最想讲清楚的一点。大多数监控失效不是因为指标没采集,而是因为阈值定得太差。

固定阈值的问题

"CPU > 80% 告警"这类规则的问题在于:不同机器的基线不同,不同时段的基线也不同。一台平时 CPU 20% 的机器在促销时跑到 60% 可能已经异常了;一台常态 75% 的批处理机器跑到 80% 完全正常。固定阈值的结果是要么天天误报,要么从不触发。

四种更好的方式

方式一:基于动作的阈值。问自己"这条告警触发时我要做什么"。 如果答案是不明确的,那这个阈值就没有意义。反过来推:设定"需要在业务受影响之前处理"作为目标,然后倒推阈值。比如磁盘要在耗尽前 4 小时被告知,就有了 predict_linear 那条规则。

方式二:分位数 / 预测型阈值。 用趋势和预测代替静态比较:predict_linear() 预测几小时后耗尽、用历史同时段的 P99 做上界。

# 当前可用内存低于过去一周同时段的 5 分位数
node_memory_MemAvailable_bytes < quantile_over_time(0.05, node_memory_MemAvailable_bytes[1w] offset 1w)

方式三:同比与环比。

  • 同比:今天 vs 上周同一天同一时刻。适合有周期性(工作日/周末、白天/夜间)的业务。
  • 环比:现在 vs 过去 1 小时。适合发现突变。
# QPS 同比上周同一时间下降超过 30%(可能是故障)
sum(rate(http_requests_total[5m]))
  / (sum(rate(http_requests_total[5m] offset 1w)) + 1) < 0.7

这条规则能捕捉到"服务彻底不可用"以外的一类问题:半死不活的状态——机器没宕但大量请求失败、或者入口出了问题导致流量暴跌。这恰恰是最容易被漏掉的场景。

方式四:多级阈值 + 持续时间。

级别 条件 通知方式 期望响应
P3 / info 轻度异常 记录,不通知或日报 不处理,仅用于趋势分析
P2 / warning 明显异常持续较久 IM 群 工作时间内处理
P1 / critical 业务已受影响 电话/短信 立即响应

每一级都要配对应的"持续时间",级别越高持续越短但不为零(P1 可以 2 分钟,P2 至少 10 分钟)。

阈值要定期回看

建议每个季度做一次告警回顾:把过去三个月触发过的告警列出来,按"是否真的需要处理"分类。从不需要处理的,调低敏感度或直接删除;应该处理的却没触发的,补上规则。告警规则是活的资产,不是设完就完。

九、监控的是症状,不是原因

最后讨论一个思维方式的问题,它决定了你在故障时能不能快速定位。

所有的监控指标,本质上都是"症状":CPU 高了、响应时间涨了、错误率上去了、磁盘满了。它们告诉你出事了,但不告诉你为什么。

举个典型的链路:

用户投诉慢 → RED 发现 P99 从 200ms 涨到 3s,错误率 2%
   ↓ 但没有 CPU/内存/磁盘/网络告警
   ↓ 继续下钻:应用层的连接池使用率指标显示 100%
   ↓ 原因:数据库连接池被慢查询占满,新请求在排队
   ↓ 下钻到中间件层:发现某条 SQL 的平均执行时间从 5ms 涨到 800ms
   ↓ 真正的原因:昨晚上线的一个查询没有走索引,数据量增大后全表扫描

这条链路上,RED 指标暴露了症状(响应变慢),但如果监控只到"CPU/内存/磁盘"那一层,你根本不会发现问题——因为机器确实没问题。真正定位到原因,靠的是分层指标体系加逐步下钻。

所以有几个推论:

第一,监控的层数越多,MTTR(平均修复时间)越短。 只有基础设施层,你得靠猜;有应用层连接池指标,你能秒锁定方向。

第二,要监控"因果链上的每一环",而不只是两端。 上面的例子里,"连接池使用率"就是中间的那环——它既不是纯资源指标也不是纯业务指标,但恰恰是最关键的定位点。

第三,不要期望一条告警能直达根因。 合理的目标是:告警告诉你"出问题了 + 大致方向",然后靠面板下钻找到具体原因。试图用一条超级复杂的 PromQL 直接算出根因,通常是过度设计。

第四,保留现场数据比告警本身更重要。 告警只在你睡着的时候响一次,而历史数据在排障时能被反复查阅。这就是为什么要在告警面板里附上 Dashboard 链接,为什么保留期要足够长,为什么用 Linkifying dashboard 的做法有价值。

常见问题

该监控多少指标?是不是越多越好?

采集上可以尽量多(成本低、排查时有用),但告警必须克制。合理规模是几百到几千个采集的指标,配 15~20 条核心告警规则。其余指标的作用是"下钻时提供上下文”,而不是被动等着触发通知。判断标准很简单:这条告警触发时,值班的人知道下一步干什么?不知道就别配。

CPU 告警阈值到底设多少合适?

不要用一个固定的百分比。建议组合条件:使用率 > 85% 且持续 10 分钟以上。对延迟敏感的业务可以更严(比如 Load/核数 > 1),批处理机器可以更松甚至不设。真正该优先设的是饱和类告警(Load/核数、队列长度),它们比使用率更早反映问题。

为什么我的监控全绿但用户说网站打不开?

最典型的几种原因:一是监控只覆盖基础设施层,没有应用层指标;二是监控本身部署在被监控的网络里,网络分区时监控一起挂了(需要做外部探活);三是 DNS、证书过期、第三方接口这类不在常规指标里的问题。建议至少加一条外部 HTTP 探活(从不同网络位置的 blackbox_exporter 打过来),以及证书过期告警。

平均值和 P95 该用哪个做告警?

用 P95 或 P99,不要用平均值。平均值会被大量快速请求掩盖掉长尾。如果想两者兼顾,可以平均值看总体、P99 告警。告警设在 P99 上,能更早发现"一小部分用户已经开始受不了"的情况。

告警总是半夜响,怎么处理?

先看是不是假阳性:绝大多数半夜告警都是因为阈值太敏感(尤其是用了瞬时值而不是持续 N 分钟)。加上合理的 for 持续时间和夜间的差异化阈值(很多告警系统在夜间可以放宽非核心告警)。真正的值班告警(P1)才应该半夜打电话。然后用抑制规则把同一个根因的多条告警合并成一条。最后,定期回顾哪些告警从来不需要半夜响应,把它们降级。

小公司只有一两台服务器,这么复杂的体系有必要吗?

不需要全做,但几件事值得做:装一个轻量监控(Netdata 或 node_exporter + Grafana)采集基础指标;配至少三条告警(主机失联、磁盘将满、外部 HTTP 探活失败);日志做集中或者至少做好轮转。这个规模下,"有没有"远比"全不全"重要。等业务规模上来了再逐步扩展到分层体系和 Alertmanager 路由。

总结

  • 筛选指标的三原则:能触发行动的才告警、优先选饱和类而非使用率类指标、每个告警都要知道异常时下一步做什么。一套几十台机器的监控,核心告警应控制在 20 条以内。
  • CPU 看四项:分项使用率(us/sy/wa/st 分开才有诊断价值)、Load(除以核数)、运行队列长度(比 Load 更敏感的瞬时排队)、上下文切换率。不要设"CPU > 60% 告警",那是噪音的来源。
  • 内存的核心判据是 MemAvailable 和 swap 进出速率(pswpin/pswpout 持续非 0 才是问题),而不是使用率。swap 占用 10~20% 但趋势平稳经常是正常的内存管理行为,要用增长率而非绝对值判断。
  • 磁盘要分开监控容量和性能:容量看空间、inode 使用率(耗尽时 df -h 还有空间但会报 No space left on device)和 predict_linear 的耗尽预测;性能看 await 和队列长度,%util 在 SSD 上参考价值下降。
  • 网络不能只看带宽:包速率(pps,小包洪水时带宽不高但 CPU 已吃紧)和重传率(超过千分之一就会明显拖累吞吐)比带宽更早出问题。应用层必看 RED 三项,响应时间必须用 P95/P99 而不是平均值。
  • 用 USE 组织资源面板(使用率/饱和度/错误),用 RED 组织服务面板(速率/错误率/耗时)。面板布局遵循从结论到细节:先健康度、再资源总览、再饱和度细节、最后分项下钻。
  • 阈值要避免固定数字:优先使用基于动作的阈值(问"触发时我要做什么")、预测型阈值(predict_linear)、同比(对比上周同时刻)与环比(对比过去 1 小时)、以及分级+持续时间(P1 用 2 分钟,P2 至少 10 分钟)。每个季度回顾一次告警规则,删掉从不需要处理的。
  • 记住监控的是症状不是原因:分层指标体系的价值在于把 MTTR 缩短——告警告诉你出事和大致方向,靠面板逐层下钻(RED → 连接池/队列 → 慢 SQL)才能找到真正的根因。同时保留足够的现场数据比告警本身更重要。