服务器半夜报警,登录之后打开硬件资源监控面板,CPU 28%,内存 60%,磁盘空间还剩一半,网卡流量曲线平得像一条直线,所有指标都在舒适区里待着,但用户侧的接口超时率已经爬到 8%。这个场景我遇到过不止一次,每一次都在提醒我同一件事:硬件资源监控的价值不在于你能采到多少个数字,而在于你能不能从这些数字里读出机器真正在经历什么。绝大多数人搭监控的第一反应是装个 agent、开个面板、配几个阈值告警,做完就觉得万事大吉,可真正上线之后才发现,告警要么不响,要么一响就是一片,面板上的曲线好看但没有任何判断力。
这篇内容想聊的是从零搭一套能用的硬件资源监控体系时会踩到的那些细节:指标该怎么取、口径怎么统一、采样频率和存储怎么权衡、告警规则怎么设计才不会半夜把人叫醒。适合已经会写几行采集脚本、但发现"数据采上来了却不知道怎么用"的同学,也适合负责一批服务器、需要给自己搭一套靠谱观测手段的运维和开发。下面这些结论大部分是我在实际环境里反复试错之后沉淀下来的,不是教科书上的标准答案,但每一条都能对上具体的现场。
1. 硬件监控真正难的不是采集,而是"判断"
1.1 采集一行代码就能做,判断却要理解整台机器
采集这件事在今天几乎没有门槛。Python 装个 psutil,一行psutil.cpu_percent()就能拿到 CPU 使用率;Linux 上直接读/proc/stat、/proc/meminfo、/proc/diskstats也能拿到裸数据;再懒一点,top、iostat、sar这些命令本身就带输出。我见过不少团队把"能采到数"当成项目完成的标志,结果面板上堆了四五十个指标,真正出事的时候没人知道该看哪一个。
问题出在硬件指标是相互耦合的。CPU 高不高,取决于你采样那一刻进程在干什么;内存看起来还有富余,可能已经被 page cache 占满、一旦有突发写入就会触发回收;磁盘空间没满,不等于磁盘健康,IO 等待时间可能已经翻了十倍。单独看任何一个数字都容易得出错误结论,把这些数字放在一起看,才有判断的余地。这也是为什么硬件资源监控不能只做成"数据搬运",它本质上是一套判断逻辑的载体。
我在搭第一版监控的时候犯过一个典型错误:把 CPU 使用率的告警阈值设成 80%,结果一台跑批处理的机器每天凌晨准时告警,值班的同学被叫起来看了三次之后直接把告警静音了。后来复盘才发现,那台机器在批处理时段 CPU 打到 90% 是设计内的行为,真正需要关注的不是瞬时使用率高,而是任务完成后 CPU 有没有回落。指标本身没有对错,脱离了业务节奏去设阈值,告警就必然失真。
1.2 把系统拆成采集层、存储层、判定层三段
经过几轮推倒重来,我现在倾向于把整条链路明确切成三层,各层职责不混:
| 层级 | 职责 | 典型实现 | 失败时的表现 |
|---|---|---|---|
| 采集层 | 按固定周期读取硬件指标,做最小限度的单位换算 | agent 脚本、node exporter 类组件 | 数据断点、时间戳漂移 |
| 存储层 | 按时间序列落盘,支持区间查询和降采样 | 时序库、关系库、本地文件 | 查询慢、磁盘写满 |
| 判定层 | 基于历史数据判断异常,触发告警或打标 | 规则引擎、脚本定时任务 | 误报、漏报、告警风暴 |
分出这三层最大的好处是排障路径清晰。有一次面板上一台机器的内存曲线突然变成一条直线,我先怀疑是采集挂了,去采集层看日志发现进程还活着,再看存储层,发现那个时间段的写入全部超时被丢弃了。如果是糊在一起的一套脚本,这个定位过程至少要花半小时。
另外,分层之后每一层可以独立演进。采集频率想从 60 秒提到 15 秒,只动采集层;告警规则想从静态阈值换成基线判定,只动判定层。这种解耦在监控这种"随时可能要改"的系统里,长期收益非常大。
1.3 硬件监控和应用监控的边界要划清楚
刚入行的时候我总想把所有指标塞进一个面板,后来发现硬件监控和应用监控的观察节奏完全不一样。硬件指标的合理变化周期通常是分钟级到小时级,应用的错误率、延迟往往是秒级。把两种节奏的数据混在一张图上,要么硬件曲线被压成一条平线,要么应用尖刺被平滑掉。
我的做法是让硬件监控只回答三个问题:这台机器现在还有多少余量、余量在往哪个方向走、有没有哪个硬件子系统已经在拖后腿。至于"哪个接口慢了"、"哪个 SQL 拖垮了连接池",交给应用侧的可观测体系去回答。边界清楚之后,硬件监控的面板反而变简单了,一张图六七个指标,值班的人扫一眼就知道要不要继续往下查。
2. CPU 与内存:最容易骗人的两个指标
2.1 CPU 使用率的采样间隔决定了这个数字有没有意义
psutil.cpu_percent()在没有传 interval 参数时,返回的是"自上次调用以来"的使用率,第一次调用永远返回 0.0。这个坑我踩过,写了个循环每秒采一次,结果每次打印出来都是上一次的值,图上看起来永远慢一拍。正确做法是显式传间隔,或者自己维护两次采样的时间戳做差分。
Linux 上更底层的方式是读/proc/stat的第一行,拿到 user、nice、system、idle、iowait、irq、softirq、steal 这些累计值,两次采样做差再算比例:
# 第一次采样 cat /proc/stat | head -1 # 睡 1 秒 sleep 1 cat /proc/stat | head -1这里有两个容易被忽略的细节。第一,iowait 算不算进 CPU 使用率,不同工具的口径不一样。top默认把 iowait 计入空闲,而有些监控面板会把它单独拆出来。如果你的机器磁盘很慢,iowait 会很高,按前一种口径看 CPU 很闲,按后一种口径看 CPU 已经很忙了。我现在的习惯是永远把 iowait 单独画一条线,不和 user、system 混在一起,因为 iowait 高说明瓶颈在磁盘而不是 CPU,处理方向完全不同。
第二,steal time 在虚拟化环境里非常关键。这个值表示 CPU 时间被宿主机上的其他虚拟机抢走了,物理机上通常是 0,云主机上偶尔会冒出来。一旦 steal 持续超过 5%,说明你这台机器的性能波动不是你自己造成的,再怎么优化应用也没用,该考虑换规格或者换宿主机了。我第一次看到这个指标的时候还以为是监控采错了,查了半天文档才明白它的含义。
2.2 load average 的数字要和核数一起看
uptime输出的三个数字是 1 分钟、5 分钟、15 分钟的平均负载。很多人把它当成"系统忙不忙"的直接指标,这是个经典误解。load average 统计的是处于可运行状态和不可中断睡眠状态的进程数,它和 CPU 核数的关系才是判断依据:4 核机器上 load 4.0 意味着刚好跑满,load 8.0 意味着有大量任务在排队。
更隐蔽的一点是,不可中断睡眠状态(D 状态)的进程也会计入 load。这意味着大量磁盘 IO 阻塞时,load 会飙升,但 CPU 可能很闲。我曾经遇到一台机器 load 常年 20 多,CPU 只有 15%,查下来是一块老磁盘的队列深度打满了,进程都卡在 IO 上。如果只看 CPU 使用率,这台机器看起来一点问题都没有。
我的经验是给 load average 配一条相对阈值:超过核数的 1.5 倍且持续 5 分钟以上才值得关注,超过 3 倍才需要告警。绝对数值没有意义,2 核机器和 64 核机器的 load 5 完全是两回事。
2.3 内存不要只看 used,available 才是能用的那个数
/proc/meminfo里有一大堆字段,新手最容易盯着MemFree看,然后发现这个值总是很小,就以为内存快用完了。实际上 Linux 会把空闲内存拿去做 page cache,MemFree小是正常现象,真正代表"还能分配多少内存给新进程"的是MemAvailable,它已经把可回收的 cache 和 slab 算进去了。
一个更完整的判断要看这几项的组合:
MemAvailable持续下降,说明内存在被真实消耗,可能是泄漏;Cached很大但MemAvailable也很大,说明缓存健康,不用管;SwapUsed开始增长,说明物理内存已经不够,性能马上要受影响;Dirty很高,说明有大量脏页等着回写,磁盘压力要来了。
我在排查一次内存泄漏时,就是靠观察MemAvailable的长期趋势定位的。业务进程的 RSS 每天涨 200MB 左右,涨幅很小,单看某一天完全看不出问题,但把 30 天的曲线拉出来就是一条稳定上升的斜线,两周之后必然 OOM。这种缓慢泄漏靠瞬时阈值永远抓不到,只能靠趋势。
2.4 容器环境下的内存指标有另一套口径
如果机器上跑的是容器,直接读/proc/meminfo拿到的是宿主机内存,不是容器可用内存。容器真正的限制在 cgroup 里:
# cgroup v2 cat /sys/fs/cgroup/memory.max cat /sys/fs/cgroup/memory.current cat /sys/fs/cgroup/memory.stat | grep -E "anon|file|slab" # cgroup v1 cat /sys/fs/cgroup/memory/memory.limit_in_bytes cat /sys/fs/cgroup/memory/memory.usage_in_bytes这里最容易踩的坑是memory.current 里包含了 page cache,容器读文件多了这个值就高,但内核在接近上限时会自动回收 cache,并不一定会 OOM。真正要盯的是anon(匿名页,也就是进程实际占用的内存)和memory.events里的oom计数。我见过一个服务因为日志文件被反复读取,cache 把容器内存顶到 95%,监控面板红了一片,实际上服务稳得很,白白折腾了一晚上。
3. 磁盘、网络、温度:藏在细节里的隐性瓶颈
3.1 磁盘空间、inode、IO 延迟是三件不同的事
大多数人监控磁盘只看使用率,df -h一跑,低于 80% 就认为没问题。我遇到过两次事故都和这个习惯有关。第一次是某个目录下堆积了几百万个小文件,df -h显示还有 40% 空间,但df -i显示 inode 已经 100%,新文件根本创建不了。第二次是磁盘空间充足,但 IO 延迟从 2ms 涨到了 200ms,应用整体变慢,监控面板上却一片绿色。
所以磁盘至少要监控三组指标:
| 指标组 | 关键字段 | 判断标准 |
|---|---|---|
| 容量 | 使用率、inode 使用率 | 两者任一超过 85% 都该关注 |
| IO 吞吐 | 读写字节数、IOPS | 和基线对比,突增突降都异常 |
| IO 延迟 | await、%util、队列深度 | await 超过基线 3 倍要警惕 |
iostat -x 1输出里的await是平均每次 IO 的等待时间,单位毫秒,这个数字比%util更能反映真实体验。%util有个著名的误导性:在支持并行处理的 SSD 和 NVMe 上,%util到 100% 并不代表磁盘饱和,因为设备可以同时处理多个请求。真正该看的是aqu-sz(平均队列长度)和await,队列长度持续大于 1 且 await 上升,才是真的堵了。
3.2 网络流量的口径不统一,图表就会打架
网络指标的口径问题比想象中严重。/proc/net/dev里的字节数是累计值,要算速率必须两次采样做差再除以时间间隔。而且网卡上的计数包含了所有流量,包括容器间通信、监控数据本身、甚至回环流量。如果机器上跑了几十个容器,容器之间互相调用的流量全部算在物理网卡上,你看到的"带宽占用"可能根本不是真实的外部流量。
我现在的做法是把网络指标拆成几个维度分别监控:物理网卡的进出字节数和包数、丢包计数、TCP 重传率、连接数。其中TCP 重传率特别值得单独盯,它比带宽更能反映网络质量。重传率超过 1% 就说明链路有问题,可能是网线、交换机端口,也可能是对端的问题。这个指标在带宽没跑满的时候也能暴露问题,是排查网络抖动的利器。
# 查看 TCP 重传统计 netstat -s | grep -i retrans # 或 nstat -az | grep -i retrans另一个容易被忽略的是网卡队列和中断分布。多核机器上如果所有网络中断都落在 CPU0 上,单核打满,其他核闲着,整体吞吐上不去。cat /proc/interrupts能看到中断分布,发现不均衡就要调整 IRQ affinity 或者开启多队列。这个问题在高流量场景下很常见,但因为它不影响 CPU 总使用率,很容易被漏掉。
3.3 温度和降频是性能波动的隐形推手
服务器也会"中暑"。CPU 温度过高会触发降频保护,性能直接掉一档,但所有软件层面的指标看起来都正常,因为 CPU 使用率还是那个数,只是每条指令执行得更慢了。我遇到过一台机器跑计算任务,同样的输入耗时从 40 分钟涨到 70 分钟,查了半天代码没找到原因,最后看温度才发现散热风扇堵了,CPU 一直工作在降频状态。
温度相关的指标至少要采这几项:CPU 各核心温度(/sys/class/thermal/thermal_zone*/temp)、风扇转速、以及 CPU 的当前频率和最大频率比值。最后这个比值很关键,如果当前频率长期低于最大频率的 80%,说明机器在做功耗或温度管理,性能是没有跑满的。
NVMe 固态硬盘的温度也要盯。这类盘在高负载下温度上升很快,超过阈值会主动限速,表现为 IO 延迟突然变大。nvme smart-log /dev/nvme0能看到温度和历史最高温度,把它纳入监控之后,那类"莫名其妙变慢"的问题定位速度快了很多。
4. 采样频率、聚合窗口与存储结构怎么定
4.1 采样频率不是越高越好,要看你想回答什么问题
新手常见的心态是"数据越多越好",把采样间隔设成 1 秒,结果一天下来单机几百万个数据点,存储成本飙升,查询还慢。实际上硬件指标的变化速度决定了采样频率的下限,不是上限。
我的经验值是这样的:CPU、内存、网络这类变化较快的指标,15 到 30 秒采一次足够;磁盘容量、inode 这类变化慢的,5 分钟一次就行;温度和风扇转速 1 分钟一次。这套组合下来,单机每天的数据量在可控范围内,同时保留了对突发问题的捕捉能力。
要注意的是,采样间隔本身会影响指标的准确性。CPU 使用率是区间平均值,30 秒采一次拿到的是这 30 秒的平均,一个持续 2 秒的尖刺会被完全平滑掉。如果你的场景需要捕捉短时尖刺,要么提高采样频率,要么在采集层额外记录区间内的最大值。我现在的做法是采集时同时记录 avg 和 max,存储层按 avg 降采样,需要看尖刺的时候查 max。
4.2 聚合方式选错,图上的数字会骗人
时序数据长期存储必然要降采样,问题是用什么方式聚合。用平均值是最省事的,但平均值会隐藏异常。一条 15 秒粒度的延迟曲线,如果某 5 分钟里有 3 次 500ms 的尖刺,聚合成 5 分钟平均之后可能只剩 20ms,看起来风平浪静。
我现在的聚合策略是按指标类型分开:
- 使用率、温度这类瞬时状态量,用平均值;
- 延迟、队列长度这类有长尾的指标,用p95 或 p99;
- 错误计数、重传次数这类累加量,用增量和;
- 容量类指标,用最大值。
拿延迟来说,p99 能保住长尾的信息,用户感受到的"卡"往往就是那 1% 的慢请求造成的,平均值看不到,p99 能看到。这个改动上线之后,我们抓到过好几次短暂但严重的抖动,之前都被平均值抹平了。
4.3 存储选型要匹配查询模式,别一上来就上重型方案
如果只是几十台机器、指标数量在几百个量级,完全没必要上分布式时序数据库。我一开始也给小集群上过重型方案,结果是维护成本远大于收益。后来改成每台机器本地写结构化文件(按小时切分),中心侧定时拉取做聚合,查询用轻量脚本,几十台机器的规模跑得很舒服。
判断依据其实就两条:数据量级和查询模式。写入频率高、需要复杂聚合查询、保留周期长的场景,用时序数据库合适;写入量不大、主要做趋势查看的场景,文件加脚本足够了。关键是别为了技术栈好看而上重方案,监控系统本身的运维负担必须可控,否则它会变成新的故障源。
还有一点是标签设计。给每个指标打上机器、机房、业务线这些标签,查询时很方便,但标签组合爆炸会让存储层性能急剧下降。我的原则是标签数量控制在 5 个以内,且每个标签的取值集合不要太大,机器 ID 这类高基数的信息放在指标名里而不是标签里。
5. 告警规则怎么设计才不会把人叫醒
5.1 静态阈值告警失效的根本原因是业务节奏
前面提过那个凌晨批处理机器告警的故事,本质上是静态阈值假设了"所有时间段的正常范围都一样",但真实系统有明显的时间节律。白天流量高、夜间批处理、周末低谷,用一把尺子量所有时段,误报几乎是必然的。
我后来把告警规则改成分时段阈值加同比对比的组合。分时段阈值很好理解,就是给工作日白天、夜间、周末各配一套阈值,配置量不大但效果立竿见影。同比对比是拿当前值和上周同一时段比,偏差超过一定比例才告警,这个做法对周期性业务特别有效。
需要提醒的是,同比对比在数据量少的时候容易被噪声带偏,最好要求连续多个周期都超标才算数,避免一次偶发波动就触发。
5.2 动态基线不是万能药,要限定适用范围
动态基线(比如按历史数据算均值和标准差,超出 3 倍标准差告警)听起来很智能,实际操作中坑不少。第一,历史数据里有故障期的数据,会把基线拉高,导致真正出问题时反而不告警;第二,业务量在增长,基线的绝对水平也在漂移,需要定期重新计算;第三,突发的合理变化(比如一次营销活动)会让基线判定完全失效。
我的做法是动态基线只用在有稳定周期性的指标上,比如内存使用率的日周期、网络流量的周周期。对于没有明显周期规律的指标,老老实实用分时段阈值加人工经验。另外一定要给动态基线配一个上下限保护,防止基线漂移到一个荒谬的区间。曾经有个指标因为历史数据缺失,基线被算成 0,导致全天都在告警,这种低级问题一定要靠边界保护兜住。
5.3 告警的持续时长和合并规则决定了它有没有用
告警最忌讳"一闪而过也报"。CPU 瞬间到 90% 然后马上回落,这通常是正常波动,报出来只会增加噪声。所以每条规则都要配一个持续时长(for)条件,比如"CPU 使用率超过 85% 并且持续 5 分钟",这样能过滤掉绝大多数瞬时抖动。
持续时长设多久也有讲究。持续的时长越长,误报越少但漏报风险越高。我的经验是分级的:影响可用性的严重指标给 1 到 2 分钟,趋势类指标给 10 到 15 分钟。分级之后,真正紧急的告警几分钟内就能触达,慢变化的指标可以慢慢看。
告警合并同样重要。一台机器磁盘满了可能同时触发容量告警、IO 延迟告警、应用错误率告警,如果三条都独立发出去,值班的人只会看到一个告警风暴。我的做法是按机器聚合,同一台机器上多条相关告警合并成一条,标明最高严重等级和涉及的指标列表。合并之后,值班同学的体验好了很多,也从"告警轰炸"变成了"一个问题一条告警"。
6. 用一段可运行的代码把采集链路跑通
6.1 采集循环的设计要点
写一个能跑的采集器不难,难的是让它长时间稳定跑下去不出错。我总结下来有几个必须处理好的点:一是时间漂移,用固定间隔 sleep 会导致采样周期越来越长,因为每次采集本身也要耗时,正确做法是按绝对时间对齐;二是异常隔离,某一项指标采集失败不能让整个循环崩掉;三是时间戳统一,所有指标用同一个采集时刻的时间戳,否则后续做关联查询会对不上。
下面这段代码是一个最小可用的采集器,覆盖 CPU、内存、磁盘、网络,输出结构化格式方便后续接入存储层:
import json import time import psutil from datetime import datetime, timezone INTERVAL = 30 # 采样间隔秒 def collect_cpu(): try: # interval=None 返回自上次调用以来的使用率 percent = psutil.cpu_percent(interval=None) load = psutil.getloadavg() freq = psutil.cpu_freq() return { "usage_percent": percent, "load_1m": load[0], "load_5m": load[1], "load_15m": load[2], "freq_current_mhz": freq.current if freq else None, "freq_max_mhz": freq.max if freq else None, } except Exception as e: return {"error": str(e)} def collect_memory(): try: vm = psutil.virtual_memory() sm = psutil.swap_memory() return { "total": vm.total, "available": vm.available, "used_percent": vm.percent, "swap_used": sm.used, "swap_percent": sm.percent, } except Exception as e: return {"error": str(e)} def collect_disk(): try: result = {} for part in psutil.disk_partitions(all=False): usage = psutil.disk_usage(part.mountpoint) result[part.mountpoint] = { "total": usage.total, "used_percent": usage.percent, "free": usage.free, } return result except Exception as e: return {"error": str(e)} def collect_network(): try: net = psutil.net_io_counters() return { "bytes_sent": net.bytes_sent, "bytes_recv": net.bytes_recv, "packets_sent": net.packets_sent, "packets_recv": net.packets_recv, "drop_in": net.dropin, "drop_out": net.dropout, "err_in": net.errin, "err_out": net.errout, } except Exception as e: return {"error": str(e)} def collect_once(): # 先触发一次 CPU 采样,让后续调用有基准 psutil.cpu_percent(interval=None) return { "timestamp": datetime.now(timezone.utc).isoformat(), "host": "local", "cpu": collect_cpu(), "memory": collect_memory(), "disk": collect_disk(), "network": collect_network(), } def main(): next_tick = time.time() while True: sample = collect_once() print(json.dumps(sample, ensure_ascii=False)) # 按绝对时间对齐,避免周期漂移 next_tick += INTERVAL sleep_time = next_tick - time.time() if sleep_time > 0: time.sleep(sleep_time) else: next_tick = time.time() if __name__ == "__main__": main()这段代码里有几个刻意的设计。psutil.cpu_percent(interval=None)在采集前先调用一次是为了建立基准,否则第一次会返回 0.0。所有采集函数都用 try/except 包住,单个指标出错不影响其他指标。时间对齐用累加next_tick的方式而不是直接 sleep,能保证长期运行周期不漂移。
6.2 指标命名要统一,不然后面全是坑
采集器写出来之后,指标命名会直接影响后续所有环节。我踩过的坑是早期对着不同机器用了不同的命名方式,有的写cpu_usage,有的写cpu_percent,等到要做聚合查询的时候,得先写一堆别名映射,非常痛苦。
现在我的命名规则是这样的:前缀表明子系统,中间是具体指标,用下划线分隔,单位作为后缀。比如cpu_usage_percent、memory_available_bytes、disk_used_percent、net_bytes_recv_total。累加量统一加_total后缀,让读取数据的人一眼就知道这个值不能直接看,要做差分。这套规则听着有点啰嗦,但半年之后回头看,它省下的时间远超过当初多敲的那些字符。
还有一个小小的经验:指标名里不要带机器 ID 或者动态值。有些人在指标名里拼上端口号或者进程 PID,结果指标基数爆炸,存储层直接扛不住。机器相关信息放到标签或者元数据里,指标名保持稳定。
6.3 从采集到可用的中间还差一步换算
采集到的裸数据不能直接上图,中间要做单位换算和速率计算。网络字节数是累计值,要算速率必须用本次值和上次值做差再除以时间差;磁盘 IO 同理;CPU 使用率已经是比例了可以直接用;温度需要除以 1000 才是摄氏度(不同内核版本的精度不一样,要确认)。
速率计算有个需要注意的地方:如果采集出现断点,两次采样之间的时间差会远大于预期,直接做差算出来的速率会严重失真。我的处理方式是给时间差设一个上限,超过两倍采样间隔就丢弃这次计算,标记为数据缺失,而不是硬算。这样图上会显示为空,比显示一个错误的高峰要诚实得多。
换算完成之后,指标才算真正可以进入存储层。我习惯在采集器里就把换算做完,输出已经带单位的可读值,而不是把原始数据丢给后端处理。这样采集层和存储层的职责更清晰,后端只做存储和查询,不做业务换算。
7. 上线之后真正会遇到的问题
7.1 告警响了但数据没断,先查采集器所在机器的资源
这套东西跑起来没多久我就遇到一个诡异现象:某台机器的所有指标突然同时变平,像是被冻住了,但采集器进程在,日志也没有报错。查了半天才发现采集器本身跑在一个内存只有 512MB 的小机器上,那个机器自己内存吃紧,进程被 swap 拖得很慢,采集周期从 30 秒变成了好几分钟。
这个教训很深刻:监控系统的资源消耗必须远小于被监控的系统。采集器要做轻量化,内存占用控制在几十 MB 以内,异常情况下也要能正常退出而不是挂在那儿。同时采集器自己也要被监控,至少要有"最后上报时间"这个指标,中心侧超过三个周期没收到数据就报警,能第一时间发现采集链路断了。
7.2 时钟不同步会让所有关联分析失效
分布式环境下最容易被忽略的是时间同步。如果不同机器的时钟有几秒甚至几十秒的偏差,采集上来的数据时间戳就对不上,做跨机器的关联分析时会得出完全错误的结论。我遇到过一台机器因为系统时间漂移了 40 秒,导致它的告警总是在其他机器之后才触发,排查了很久才发现是时钟问题。
现在我的做法是给采集器加一个时间偏差自检,定期和一个基准时间源对比,偏差超过阈值就记录下来并标记这条数据不可信。另外所有时间戳统一用 UTC 存储,展示的时候再转本地时区,能避免夏令时、时区切换带来的混乱。
7.3 保留周期要根据排障习惯来定,不是越久越好
数据保留多久合适,这个问题没有标准答案,但可以按实际排障需求推。我自己的习惯是:原始粒度数据保留 7 天,够覆盖一个完整的工作周期;5 分钟聚合数据保留 30 天,能看月度趋势;1 小时聚合数据保留一年,用于容量规划。这套分层保留策略下来,存储成本可控,需要的粒度也都能找到。
一个反直觉的经验是:保留周期太长反而会增加排障难度。数据太多的时候,查询会变慢,图表的粒度选择也变复杂了。我经历过一次把保留周期从三个月提到一年,结果每次打开面板加载都要等好几秒,值班同学反而更不愿意用了。控制在"够用"的范围,比追求"全都留着"更实际。
8. 最后聊聊这套体系长期运维的体会
搭完之后最大的感受是,监控系统本身是需要持续打磨的,不是一次性工程。每隔一段时间我都要回头看看告警历史,统计哪些规则从来没触发过、哪些规则触发了但事后确认是误报。从来没触发过的规则不一定是好规则,也可能是因为阈值设得太松,真正的问题被漏掉了。定期做一次规则复查,把误报的规则调紧、把漏报的规则补上,这套东西才能一直保持有用。
另一个体会是,别追求指标的全覆盖,先把手头最常用来排障的那几个指标做扎实。CPU、内存、磁盘空间、IO 延迟、网络重传、温度,这六类指标覆盖了我遇到过的九成以上硬件问题。等这六类用顺手了,再根据具体业务补充其他指标,比一开始铺开一大堆要有效得多。
还有一个不太起眼但很有用的技巧:给每个告警附上排查建议。告警通知里不光写"CPU 使用率 92%",还写清楚"先看 top 里的进程排名,再确认是否在批处理时段",能显著降低值班同学的上手门槛。这套建议是长期积累出来的,每处理完一个问题就补充一条,慢慢就形成了一份贴合自己环境的排障手册。这套东西没法抄,只能自己一台机器一台机器地攒。