接手这套分布式系统的时候,监控基本处于"裸奔"状态。Prometheus和Grafana是有的,但覆盖的只有集群层面的几个常规指标:节点存活、总CPU、总内存、网络吞吐。节点内部的性能细节完全是一团黑。真正把我逼到必须补单机监控这一步的,是一次线上事故:某个节点CPU被打满,Prometheus告警了,但等我要复盘时,Grafana上只有分钟级的聚合数据,根本看不出来这波异常是哪个进程先发起、哪个指标先变化、CPU Wait是不是早就开始抬头的。缺了最关键的那几分钟的细节,故障复盘就只能靠猜。所以后来我给这套系统重新梳理监控体系时,把节点级原始数据采集当成了一件正事来做。本文就从我的实际操作出发,讲讲分布式系统监控的整体分工,以及我用nmon(即nmon监控工具)在aarch64架构节点上做逐节点性能采集的整套过程和心得。
当时团队里也有人问:既然有Prometheus了,为什么还要在每台机器上单独跑一个采集工具?这个问题的答案,其实也正是分布式系统监控里最容易被人忽视的部分——告警层面和取证层面是两回事,监控方案不能只解决"有没有报警",还要解决"出事后能不能查清楚"。下面我会从监控清单怎么列、nmon这种单机工具在整个体系里扮演什么角色、以及在ARM 64位(aarch64)环境下怎么把nmon部署成常驻服务、采集到的文件怎么分析,一步步讲清楚。
1. 接手这套分布式系统后,我先列了一张监控分工清单
分布式系统监控最容易犯的错,就是试图用一个大而全的平台解决所有问题。真正靠谱的做法是先把监控目标按层次拆开,每一层用合适的工具,而不是一个工具通吃。
我给自己列的清单分了三层:集群层、节点层、应用层。每层解决不同的问题,回答不同的问题。集群层回答"整个系统现在健康吗",节点层回答"某个节点现在具体卡在哪",应用层回答"用户的这次请求为什么慢"。缺了任何一层,排查链路都是断的。
1.1 三层监控的分工:集群层、节点层、应用层
下面这张表是我当时梳理的基础框架,后面所有工具选型都是围绕它来的:
| 监控层级 | 核心问题 | 常用工具 | 典型指标 |
|---|---|---|---|
| 集群层 | 系统整体是否可用 | Prometheus + Grafana、Zabbix | 节点存活、集群CPU总使用率、内存水位、网络吞吐、告警事件 |
| 节点层 | 单台机器发生了什么 | nmon、atop、sar(sysstat) | CPU各项占比、内存、磁盘I/O、TCP连接数、上下文切换、TOP进程 |
| 应用层 | 业务请求为什么异常 | SkyWalking、OpenTelemetry、日志聚合 | 链路耗时、错误率、慢SQL、接口P99延迟 |
Prometheus这类工具在集群层做得非常好,我到现在也认为它是分布式系统监控的地基。但它有个天然的特性:它采集的是一组预先定义好的指标,而且是按固定间隔拉取的,默认15秒一次,落到Grafana里还要再经过聚合。这个精度用来做告警足够了,用来做故障根的细节分析远远不够。
节点层恰恰是很多人会跳过的。我在梳理时发现,如果不做节点级的原始数据留存,遇到那些"偶发性CPU飙高""内存缓慢泄漏""磁盘I/O抖动"类问题时,基本只能等下一次复现。而nmon这类单机工具的价值,就是在一台机器内部做高频采样,把CPU、内存、磁盘、网络、进程级别的数据按秒级间隔原原本本记录成文件,放着备用。
应用层则是业务可观测性的范畴,和本文关系不大,但我要强调:三层必须齐头并进,只做任何一层都会在关键时刻抓瞎。
1.2 节点层工具选型时,为什么nmon被留到了最后
节点层可选的工具不少,atop、sysstat(sar)、nmon是三个主流。当时我也纠结了一番。atop的功能很强,支持进程级历史回放,但它是一个常驻daemon,需要安装完整软件包,在某些精简的ARM系统上依赖库不一定齐全。sysstat是老牌工具,sar采集的数据可靠,同样需要安装一堆依赖,而且默认采集项偏内核统计,进程级细节要靠额外配置。
nmon不一样的地方在于:它是一个静态编译的单个二进制文件,不依赖Python、不依赖动态库、不需要安装守护进程。下载下来、给个执行权限、跑起来就直接采。它背后是由IBM工程师Nigel Griffiths发起并持续维护的开源项目,提供了x86、ARM、PowerPC、S390等各个架构的预编译版本。对于我这套有大量aarch64节点的分布式环境,这种"零依赖、单文件、多架构"的特性简直是救命稻草。
所以nmon反而成了我最后拍板要上的工具。它不替代Prometheus,也不替代atop,它替代的是"节点上裸奔、没有本地性能历史"这个空白。
2. 为什么集群监控已经就绪,我仍坚持在每个节点上跑nmon
这一节我得先把一个反直觉的观点讲清楚:Prometheus的告警覆盖面越广,你对节点级原始数据的需求反而越强烈。因为告警只是告诉你"出事了",而故障复盘真正需要的,是"从正常到异常,每个指标是怎么一步步变化的"。
2.1 Prometheus告诉你去哪里,nmon告诉你发生了什么
举一个我实际遇到的例子。那次线上事故,Grafana上的集群总CPU曲线从10%突然拉到90%,告警响了,值班同事第一时间去查。但集群层的图表粒度是分钟级的,打开看只能看到一条陡峭的上升线,完全看不到上升之前有没有先兆。是哪个进程先起来的?是CPU计算密集,还是系统调用频繁?内存有没有同步变化?这些信息,Prometheus的Node Exporter默认指标里其实也有,但一是存储周期有限,二是聚合后精度丢失,三是查起来要写PromQL,远不如直接翻原始采样记录来得快。
nmon做的就是把每个节点的性能数据按秒级间隔写成本地文件。CPU_ALL这一行会记录User%、Sys%、Wait%、Idle%四个细分值,Wait%一旦明显抬头,基本可以判断问题出在磁盘I/O等系统等待上,而不是单纯的运算密集。单凭这一项,就能把排查方向从"CPU坏了?"修正为"存储链路有问题",省下一整天的冤枉路。
在我看来,Prometheus解决的是监控的广度——覆盖所有节点和核心指标,保证第一时间发现问题;nmon解决的是监控的深度——在某个节点上留下完整、原始、秒级的性能档案,保证事后能还原真相。两者不是替代关系,是互补关系。特别是分布式系统里节点几十上百台,如果你每台机器上都有一份完整的历史档案,复盘任何一次故障都会轻松很多。
2.2 nmon的运行模型:一个静态二进制如何完成高频采样
很多人第一次拿到nmon会有点懵:就一个二进制文件,双击(或者说执行)起来,怎么就能持续采集那么多指标?
其实原理并不复杂。nmon本身内置了对Linux各种统计接口的读取逻辑,通过读取/proc下的各种文件来拿到CPU、内存、磁盘、网络、进程等数据。它有两种工作模式:
- 交互模式:直接执行
./nmon,进入全屏字符界面,按c看CPU、按m看内存、按d看磁盘、按n看网络,适合临时登录服务器快速看一眼当前负载。 - 采样模式:用
-f等参数让它后台运行,周期性采数据并写入文件,适合做长期采集留档。
我日常用的是第二种。采样模式下,nmon会按你指定的间隔(秒)和次数持续取样,所有数据按行写入一个.nmon文本文件。文件是可读文本,不是二进制,这意味着即使身边没有任何分析工具,你也能直接用grep、awk去翻数据,这一点在排查问题时非常管用。
它采集的原始数据里,会包含不少格式标签。注意文件里的这些行:
CPU_ALL开头的行:整机CPU汇总,细分User、Sys、Wait、Idle等。MEM开头的行:内存使用情况,包括物理内存、交换分区、缓存。DISKBUSY开头的行:每块磁盘的忙闲百分比。NET开头的行:每块网卡的读写速率。TOP开头的行:占用资源最高的进程列表。
这些就是后续所有图表工具的输入。理解了这一层,你就能明白:数据分析只是手段,原始数据才是资产。nmon带给你的不是现成的报表,而是一份你可以反复翻查的时间序列原始样本。
3. aarch64节点落地nmon:从下载到systemd托管
我的这套分布式环境里,有一部分节点是ARM架构的服务器,系统是64位的,也就是aarch64。真正动手部署nmon之前,我建议你先花一分钟确认一下节点架构,否则后面的功夫可能白做。
另外说一句,很多人搜索的时候会把架构名写成"arrch64",我一开始也看懵了。其实实际架构名是aarch64,官方叫法AArch64,指的是ARM的64位执行状态。你在Linux上执行uname -m,看到的输出通常是aarch64。搜索词里出现arrch64,多半是手误或者搜索引擎的联想纠偏。不管怎样,认准aarch64就行。
3.1 先确认架构:aarch64和arrch64其实是什么
部署前,我习惯先跑三条命令:
uname -m # 查看机器架构,aarch64就对了 getconf LONG_BIT # 看系统位数,64就是64位 cat /proc/version # 顺带看内核版本,有些老内核可能影响nmon某些指标uname -m输出aarch64,getconf LONG_BIT输出64,这样的节点就可以放心选择nmon的aarch64版本。如果输出是x86_64,那就用x86_64对应的nmon版本。注意不要只看发行版名称,很多ARM设备的发行版和x86版长得一模一样,但架构完全不同,下错二进制文件会直接报"cannot execute binary file"。
另外提醒一点:嵌入式ARM板子有些是32位的,uname -m会输出armv7l之类,那属于armhf或armel分支,和aarch64不通用。我这套环境里全是64位ARM服务器,所以只关注aarch64。
3.2 在ARM节点上把nmon跑起来:完整命令记录
nmon的官方发布渠道在SourceForge上,项目名是nmon。进入下载页面后,你会看到类似nmon16n.tar.gz这种打包文件,里面包含了各个架构的预编译二进制。
我的标准部署流程如下,整个过程大概三分钟:
# 1. 建目录,下载并解压 mkdir -p /opt/nmon cd /opt/nmon # 假设压缩包已经下载到 /root 目录 tar zxvf nmon16n.tar.gz ls -la解压后你会看到很多文件,命名风格类似nmon_aarch64、nmon_x86_64_centos7、nmon_s390x等。这时候选出我们需要的:
# 2. 把aarch64对应的二进制复制到系统目录并授权 cp nmon_aarch64 /usr/local/bin/nmon chmod +x /usr/local/bin/nmon # 3. 验证版本,能打印出版本号就说明二进制可用 nmon -V第一次建议先用交互模式跑一下,输入./nmon,看字符界面能不能正常刷新。能进入界面后按c、m各看一眼,确认CPU和内存数据正常,然后按q退出。这一步能提前暴露某些老内核上的兼容问题。
确认没问题后,就可以正式用采样模式采集了:
# 4. 后台采样:每5秒采一次,共12次(1分钟),数据存到/var/log/nmon,同时记录TOP进程 mkdir -p /var/log/nmon nmon -f -s 5 -c 12 -m /var/log/nmon -t这条命令的参数含义我整理了一下,后面调参数时用得着:
| 参数 | 含义 | 示例 |
|---|---|---|
-f | 后台采样模式,数据写入文件 | -f |
-s | 采样间隔,单位秒 | -s 5表示每5秒一次 |
-c | 采样次数 | -c 12表示采样12次 |
-m | 指定输出目录 | -m /var/log/nmon |
-t | 额外记录TOP进程信息 | -t |
-F | 指定输出文件名 | -F node01_0517.nmon |
执行完后去/var/log/nmon目录看一眼,会生成一个类似主机名_日期_时间.nmon的文件,比如node01_250228_1500.nmon。用head -20看一眼内容,如果能看到CPU_ALL、MEM这些标签行,说明采集已经正常工作了。
3.3 用systemd把它变成常驻服务,别让人肉起停
监控工具最怕的就是"忘了开"。重启一次机器,nmon没有自动拉起来,故障就正好发生在空窗期,你连哭的地方都没有。
我会用systemd管理nmon,让它在开机时自动启动,并且崩溃后自动拉起。网上不少教程推荐用crontab的@reboot方式,也能用,但systemd能提供更细的进程管理和重启策略,我这边统一的习惯是用unit文件。
在/etc/systemd/system/nmon.service写入如下内容:
[Unit] Description=nmon data collector for node After=network.target [Service] Type=forking ExecStart=/usr/local/bin/nmon -f -s 30 -c 2880 -m /var/log/nmon -t Restart=on-failure RestartSec=30 [Install] WantedBy=multi-user.target然后执行:
systemctl daemon-reload systemctl enable nmon systemctl start nmon systemctl status nmon这里有个很关键的细节,我在Type这里踩过坑:nmon加上-f参数后,主进程会fork一个子进程去后台写数据,然后自己退出。如果这里写成Type=simple,systemd会认为服务进程已经退出,再配合Restart=on-failure就会反复拉起多个采集进程,导致数据文件混乱。所以要看清楚,Type必须写成forking,这样systemd会等待父进程退出并确认子进程在跑,服务状态才是正常的。
上面的参数-s 30 -c 2880算出来的是一次采集任务的总时长:30秒x2880次=86400秒,正好24小时。nmon在执行完-c指定的次数后会自动退出,这意味着用systemd托管时,它其实是一个"会自然结束"的服务。对这个问题我的处理方式是:unit里不追求常驻,而是靠一个系统自带的timer或者crontab让它每天自动重新拉起。比如加一条crontab:
# 每天0点重新拉起nmon,开始新一天的采集 0 0 * * * /usr/bin/systemctl restart nmon这样每天都会生成一个新的.nmon文件,存档按天分隔,既方便管理,也避免单个文件无限增大。
4. 分析.nmon文件的两个思路:命令行快查与图表复现
数据采下来了,光躺在磁盘里没用,关键是怎么快速提取出有效信息。我平时分析nmon文件分两种情况:故障发生时争分夺秒,直接用命令行翻原始文本;平时做复盘或写报告,则会把.nmon文件转换成图表。
4.1 不装任何依赖,怎么快速用grep和awk定位问题
先说说命令行快查。nmon文件是纯文本,所以很多Linux原生的文本处理工具都能直接上。
比如要看整机CPU的变化规律,直接抓CPU_ALL行:
grep "CPU_ALL" /var/log/nmon/node01_250228_1500.nmon | head -20输出的每行大致是T0001,CPU_ALL,30.5,2.1,5.6,61.8这种结构,前一个数字是采样序号,后面分别对应User%、Sys%、Wait%、Idle%。如果你发现Wait%一直高,而User%不高,那基本可以断定瓶颈在I/O等待,CPU还在空转等磁盘或者网络。
再看内存,抓MEM行:
grep "^MEM" /var/log/nmon/node01_250228_1500.nmon | head -20内存行包含物理内存总量、可用量、缓存量、交换分区用量等。如果发现可用内存一路走低而缓存一路走高,就要考虑是不是有进程在做大量文件读写,占用了page cache。
要算一个时间段内的平均值,结合awk就行。下面这条命令计算所有CPU_ALL记录的平均User%、Sys%、Wait%:
grep "CPU_ALL" /var/log/nmon/node01_250228_1500.nmon | awk -F',' '{sum1+=$3; sum2+=$4; sum3+=$5; n++} END {printf "User: %.2f%%, Sys: %.2f%%, Wait: %.2f%%\n", sum1/n, sum2/n, sum3/n}'注意nmon文件里各行的字段顺序可能随版本略有变化,我建议你先用head看一眼实际列位置再写awk,别生搬硬套。
如果还想定位是哪个进程在消耗资源,看TOP行:
grep "TOP" /var/log/nmon/node01_250228_1500.nmon | head -30TOP行会记录占用CPU百分比最高的那几个进程的信息。虽然粒度比perf差很多,但作为第一层过滤工具绰绰有余。
4.2 把.nmon变成图表:用nmonchart和nmon_analyser
命令行快查适合自己排查,但给团队同步结论时,图表直观得多。nmon生态里有两个常用的图表方案:nmonchart和nmon_analyser。
nmonchart是一个ksh脚本,把.nmon文件转成一个自包含的HTML文件,里面用JavaScript画好了CPU、内存、磁盘、网络的时间序列图。用法也不复杂,先把脚本和依赖准备好:
# 装ksh,nmonchart依赖这个shell yum install -y ksh # 进入nmon目录,假设脚本在 /opt/nmon/nmonchart cd /opt/nmon ksh nmonchart /var/log/nmon/node01_250228_1500.nmon /tmp/node01_250228_1500.html生成完的HTML文件直接用浏览器打开,全是一张张时间序列曲线,CPU四项、内存、磁盘I/O、网络吞吐,按时间轴对齐,非常适合做故障时间线比对。这个方案完全离线运行,不依赖外网,内网环境也走得很顺。
另一个方案是nmon_analyser,它是一个Excel宏工具,运行在Windows或macOS的办公电脑上。你需要把.nmon文件从服务器拷贝到本地,然后在Excel里打开nmon_analyser,启用宏,选择文件,它会自动分析并生成一套详细报表,包含平均值、最大值、TOP进程排名等,格式长得比较正式,适合直接剪到故障报告里。
我的习惯是:技术排查优先用nmonchart,因为它快、全、在Linux本地就能搞定;写正式报告时用nmon_analyser,因为它的汇总表和统计项更整洁。两个工具各有各的场合,反正都不欠依赖,装好一次就能用很久。
5. 这套监控方案实测中踩过的坑和取舍
再顺的工具,落地上也免不了踩坑。这一节算是我在分布式系统节点上用nmon大半年后,实打实给后面的运维同学排雷。
5.1 采样参数设置不当时,文件大小轻松膨胀几十倍
我最早在某一台性能测试机上跑过激进参数:-s 1 -c 86400,也就是每秒采一次,连续采24小时。结果第二天一看,.nmon文件干到了几百MB。更要命的是,nmon高频采集本身会消耗一定的CPU和I/O,在严苛环境里这等于给自己加了额外负载。
后来我定了这么一套参数策略:
- 日常采集:
-s 30 -c 2880,也就是30秒一次,一天一归档。 - 故障复现期:临时用
-s 1 -c 600跑10分钟,把现场细节抓下来,用完就停。
这里的关键思路是区分"常态化留存"和"临时性取证"两个场景。日常采集不需要太高频,因为绝大多数故障都能通过分钟级数据确定大致时间点;只有已经发现异常、需要深挖根因时,才值得用秒级间隔做短窗口的密集采样。这样既控制了文件体积,也兼顾了采集精度。
5.2 时间戳总是差8小时:时区问题一定要提前统一
有一次复盘,我发现nmon数据里的CPU峰值时间和业务日志里的异常时间总是错开一段。后来仔细一看,是节点系统的时区设置不统一:有的节点是UTC,有的是CST(东八区),而写入nmon文件名和记录的本地时间因此差出了8小时。
这个问题看似不大,但在分布式系统多节点对账时非常致命。你拿着三台节点的数据画同一张时间线,如果它们的时区不统一,所有的时间对齐都是错的。
我的处理方式是两件事一起做:一是把节点时区统一成业务所处时区的标准,用timedatectl set-timezone Asia/Shanghai这类命令固定下来;二是在systemd的ExecStart里用-F参数给输出文件命名时带上明确的时间标记,比如node01_$(date +%Y%m%d_%H%M).nmon,确保看到文件名就能判断是哪个时段的快照。
5.3 虚拟化环境里的磁盘指标会"说谎"
我发现很多云主机上nmon采集到的磁盘I/O数据,看起来非常低,但业务却明显出现了存储性能问题。原因在于:nmon读取的是虚拟机的虚拟磁盘统计,这些数值反映的是虚机内看到的I/O,而宿主机层面的磁盘竞争、缓存命中率等,虚机内部往往是感知不到的。
类似的,CPU的Wait%在虚拟化环境下也会出现虚高。比如宿主机的邻居在疯狂做磁盘操作,你的虚机CPU可能被调度延迟拉高Wait%,看起来像是本机磁盘出问题了,但实际上是宿主机层面共享存储的瓶颈。
所以用nmon分析虚拟化节点时,我给自己定了一个原则:单看磁盘指标不能下结论,必须同时看网络延迟、TCP重传、CPU上下文切换等指标,综合判断是虚机内部问题还是宿主机层面问题。这也是分布式系统监控里常说的"指标交叉验证",单点数据只能提供线索,交叉验证才能靠近真相。
5.4 systemd托管nmon时,进程模型导致的反复重启
这个坑我前面稍微提过,这里展开说说。最初我把nmon.service的Type写成simple,结果启动后systemd认为主进程退出了,因为它确实fork完就返回了,然后Restart=on-failure开始起作用,隔几十秒就重启一次服务。这样一来,同一时间段可能存在多个nmon进程写文件,采集的数据互相交错,极其混乱。
我把unit改成Type=forking之后,问题才彻底解决。如果你在抓这个问题时发现自己ps -ef | grep nmon能看到多个进程,不用怀疑,十有八九是Type配错了。处理方式就是我前面写的:改Type=forking,然后systemctl daemon-reload,再systemctl restart nmon。这是个小坑,但真的能卡住一大半第一次配systemd托管nmon的人。
最后再分享一个我自己养成的习惯:每台节点的/var/log/nmon目录,我会配一个定时任务,每天凌晨把前一天的.nmon文件用gzip压缩并按月归档,保留90天就清理。Prometheus的存储空间有限,很多时序数据过几周就查不到了,但nmon的原始文件只要几十MB,存上三个月几乎没压力。真遇到那种需要翻一个多月前某次性能抖动记录的场合,这批原始文件就是整个监控体系里唯一的"监控录像机"。这套架构我用了大半年,日常故障复盘的效率提升非常明显,强烈建议在分布式系统里每个节点都配上。