说到Linux性能监控,很多书里讲的第一条命令往往是top,但我个人在真实故障现场敲得最多的,其实是vmstat。这名字全称是virtual memory statistics,中文叫虚拟内存统计,听着好像只管内存,实际上它把你机器上CPU、物理内存、交换分区、块设备I/O、系统中断、上下文切换一次全拉出来,十几行数字就是一张完整的系统运行快照。这篇文章想把这套命令彻底讲明白:每个字段是什么含义、怎么用定时间隔连续采样、遇到性能问题怎么通过几列数字的组合快速缩小排查范围。刚接触Linux的运维新人可以把它当入门教材,有基础的老人也能对照着捡回一些细节,后面还会把我实操中踩过的坑和判断套路一起分享出来。
1. vmstat的核心价值与基本用法
1.1 为什么故障排查时先敲vmstat
我见过不少同事,机器一慢就盯着top看,盯了半天只能得出"CPU好像满了"这种模糊结论。top当然有用,但它默认显示的是进程级别的实时状态,缺少一个"整体视角"。vmstat就不一样,它把系统拆成procs、memory、swap、io、system、cpu六个块,一次采样就能看到全局。
另外还有个实用理由:vmstat输出是纯文本、格式固定,特别适合丢进脚本里做定时采集。我在排查线上问题时习惯先跑一个vmstat 1 10,连续采10个点,再用awk把某一列拎出来看趋势,比反复看top截图高效得多。配合它的还有一点值得提——vmstat命令本身开销极低,读取的是/proc下的虚拟文件,不会对正在抖动的业务系统造成额外负担,这在故障现场非常宝贵。
还有个小细节:vmstat虽然名字叫"虚拟内存统计",但它其实是从/proc/stat、/proc/meminfo、/proc/vmstat等文件汇总出来的系统级视图。理解了这一层,你就知道它展示的是整机状态,想看具体是哪个进程导致的异常,得继续用pidstat或top去定位,vmstat负责的是"指方向"这一步。
1.2 命令格式、参数和最简单的上手方式
vmstat的基本使用格式是vmstat [options] [delay [count]],这个设计很有Unix风格:不写delay和count就只输出一次;写了delay不写count就无限采样直到你按Ctrl+C;两个都写就按固定次数执行。
新手建议直接记住两个最常用的组合:
vmstat:看当前快照vmstat 1 5:每隔1秒采一次,共采5次,适合观察短时波动
除了这两个,下面这些参数在实战中也很常用,我整理成一个速查表:
| 参数 | 作用 | 我的使用场景 |
|---|---|---|
-a | 显示active和inactive内存,比默认内存列更直观 | 想看匿名页和文件缓存分别占多少时 |
-w | 宽屏输出,列宽加大 | 在宽屏终端里让数字对齐,避免读串行 |
-t | 每行输出附加时间戳 | 配合脚本做长时采样,方便对齐业务日志时间点 |
-s | 以汇总形式显示内存统计 | 快速查看内存总量、已用总量、页换入换出累计值 |
-d | 显示每个磁盘的读写统计 | 定位到具体磁盘的I/O情况 |
-S M | 内存数据统一以MB为单位显示 | 大内存机器上读数字更舒服 |
举个例子,你想每2秒采一次,采60次,顺手把时间戳打上,命令就是vmstat -t -w 2 60。输出里每行前面会多出时间,方便后面丢进监控系统或跟应用日志做对照。这种写法我在排查"每次整点服务抖动"之类的规律性问题时特别常用。
有一点必须提醒:delay的最小值是1秒,你写vmstat 1就是每1秒刷新一次。但如果你写vmstat 0.5,多数版本会提示非法参数,因为vmstat不支持小数秒采样。想要更细粒度只能自己用脚本转,不过对99%的系统排查来说,1秒粒度已经足够了。
2. 输出字段逐列拆解:每个数字背后代表什么
默认执行一次vmstat,输出大概长这样:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 2 0 0 8384860 234568 4102356 0 0 5 18 120 180 2 1 97 0 0第一行是列名,第二行是采样数据。下面我把每一列拆开讲,这部分是重点中的重点——很多排障思路其实就藏在这些列的组合关系里。
2.1 procs两兄弟:r和b
r表示当前处于可运行状态(runnable)的线程数,也就是正在CPU上跑的、以及排队等CPU的线程总数。注意这里统计的是线程,不是进程。判断CPU是否饱和有个经典经验:如果r长期大于CPU逻辑核数,说明任务在排队,CPU已经忙不过来了。比如一个4核机器,r值长期在6到8徘徊,那基本可以断定CPU是瓶颈。
b表示处于不可中断睡眠状态的进程数,这类进程通常是在等I/O(比如等磁盘读写、等网络数据)。b列如果持续不为0,最常见的原因是磁盘I/O太慢或有阻塞,这时候要重点看下面的wa和bi/bo列。我在数据库服务器上见到过b值长期保持在2到3的情况,最后定位是日志盘满了导致写操作挂起,所以b列异常时别只盯着磁盘性能,也要检查有没有进程卡在文件系统层面。
2.2 memory四件套:swpd、free、buff、cache
swpd是已经用到交换分区(swap)上的内存量,单位KB。它是个累计水位,只要不增长就说明系统是稳的——即使它一直保持着几GB的值,只要si/so是0,就不算内存压力。
free是空闲物理内存,这个数当然越高越好,但不要一看到free小就慌。buff是块设备的读写缓冲区,cache是文件系统的页缓存。Linux系统会尽量把空闲内存用作buff/cache提升I/O性能,所以你会发现刚开机时free还能看,跑几天后free往往只剩几百MB,而cache占了好几GB——这是正常现象,不是内存泄漏。
判断内存是否真紧张,主要看两部分:一是free还剩多少,二是si/so有没有变化。如果free充足,即使cache占满也无所谓;如果free见底、cache也被迫回收,同时si/so开始有动静,那才是真正的内存危机。
2.3 swap两个关键数字:si和so
si是从swap换入内存的速率,so是从内存换出到swap的速率,单位都是KB/s。这两个数值默认是每秒变化的快照,反映的是"换页活动"是否在发生。
我判断内存压力的标准很简单:si/so只要长期保持0,你就当swap不存在;一旦si或so持续有非零值,说明物理内存真的不够用了,系统正在靠交换分区续命。这时候应用的响应时间会显著变差,因为内存页在磁盘和内存之间来回搬,速度比物理内存慢几个数量级。
有人会问:si/so偶尔出现一个非零值要紧吗?这要看持续时间和振幅。如果只是某一次突然有几十KB的换入,可能是某个进程刚启动把冷页读回来,不用太紧张;如果si和so稳定保持在几百KB/s以上,且持续几分钟,就必须处理了,否则后面等待你的就是整机性能雪崩。
2.4 io两个指标:bi和bo
bi是从块设备读入的数据量,bo是写入块设备的数据量。在较新版本的procps-ng中,这两个字段单位是KiB/s,老版本里可能显示为每秒块数,建议用前先man vmstat确认一下单位。
bi/bo的意义在于和wa列配合判断I/O瓶颈。比如你看到wa很高、b非零,再看bi或bo中有一个数值很大,就能确认是I/O方向的问题。读多还是写多也一目了然:bi高说明读密集,bo高说明写密集。如果配合vmstat -d还能定位到具体是哪个磁盘在忙,这个后面实战部分会细说。
2.5 system两个计数器:in和cs
in是每秒中断次数,包括时钟中断和设备中断。cs是每秒上下文切换次数,也就是CPU在不同线程之间切换的频率。这两个数本身没有绝对的好坏标准,但结合场景看很有信息量。
我举个典型例子:某台机器CPU使用率只有30%左右,但cs每秒高达七八万次,应用响应还是慢。这种情况往往是"线程风暴"——应用里开了几百上千个线程,大家疯狂抢锁、阻塞、唤醒,CPU的大部分时间都花在切换线程上下文而不是干正事。排查方向就变成:为什么有这么多线程?谁在频繁唤醒它们?这种问题不借助cs列很难发现,因为CPU看起来没满。
上下文切换本身是系统正常运行的一部分,Java服务每秒几千次cs完全正常,但如果涨到数万甚至十万级,就要警惕了。
2.6 cpu百分比五行(或四行)
us是用户态CPU占比,也就是跑应用代码所占的时间;sy是内核态CPU占比,系统调用、中断处理、锁操作都算在内;id是空闲比例;wa是CPU等待I/O完成所占比例;st是被虚拟机管理程序"偷走"的时间比例。
这里有个容易误读的点:wa高不一定是I/O设备坏,而是CPU把时间花在等I/O结果上。如果wa长期超过20%,基本可以认定I/O子系统在拖后腿。还有个判断技巧:us很高代表业务代码在烧CPU,sy很高可能代表系统层面有异常(比如频繁中断、系统调用过多、锁竞争严重)。
st这一列在物理机上永远是0,但在云主机上可能很扎眼。如果你用的云主机出现st明显偏高,说明宿主机上其他虚拟机在抢CPU资源,你花在CPU上的钱没换来对应性能。这种情况你调整自己系统内的参数没用,只能考虑迁移实例或升级到独享CPU规格。
3. 实战案例:几个场景快速定位系统瓶颈
讲了这么多字段,不上实战等于白讲。下面我模拟几个我在工作中真实遇到的场景,用vmstat一步步缩小范围。你可以直接在测试机上用命令复现,也可以看完思路后用在工作里。
3.1 案例一:应用卡顿,CPU被谁吃了
现场:业务反馈页面响应慢,登录服务器看了一眼,系统负载(load average)从平时的2涨到了15。
操作:敲下vmstat 1 5,输出如下:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 9 0 0 234567 89012 4567890 0 0 0 0 32000 92000 96 3 1 0 0 11 0 0 234520 89012 4567890 0 0 0 0 33000 95000 95 4 0 0 0 10 0 0 234498 89012 4567890 0 0 0 0 31000 89000 96 3 1 0 0解读:先看r列,稳定在9到11,而我这台机器是8核心,r已经明显超过核数,说明CPU排队严重;再看us列96%、id基本为0,说明CPU确实被用户态代码占满;cs每秒九万多次,进一步说明线程切换极频繁。
结论与动作:业务进程把CPU吃光了。接下来用top -c或pidstat -p看一下具体是哪个进程、哪个线程最烧CPU,然后用jstack(Java场景)或perf(C/C++场景)进一步抓热点。如果是升级了版本后突然出现,优先怀疑代码死循环、正则回溯、全表扫描之类的变更。
如果想在测试机上复现这个场景,可以跑几个死循环脚本,比如while true; do :; done &,跑8个左右就能把多核CPU打满,再敲vmstat就能看到类似画面。
3.2 案例二:内存告警,系统靠swap续命
现场:监控系统发来告警,剩余物理内存不足500MB,应用日志出现大量"Cannot allocate memory"。
操作:执行vmstat 1 10,看到现象是si/so持续非零:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 3 2 512340 89720 56214 2145678 45000 32000 46000 33000 15000 28000 20 15 45 20 0解读:swpd已经512MB且还在涨,free只有不到90MB;si每秒45MB、so每秒32MB,说明系统一边把老页换出到swap、又一边把需要的页换进来,这个状态就是典型的内存颠簸。再看io和wa,bi高、bo高、wa也高,这些其实都是内存压力引发的"次生灾害"——swap换页本身就要读写磁盘,把I/O也拖下水了。
结论与动作:物理内存严重不足,不能靠调参数硬扛。先查最大的内存占用进程(top按内存排序或ps aux --sort=-rss),检查是否存在内存泄漏;如果业务确实需要更多内存,直接扩容物理内存或把部分非核心进程迁移走;临时手段可以调低page cache(sysctl vm.swappiness),但这只是延缓,救不了命。
复现场景可以在测试机上用Python申请大量内存,例如跑python3 -c "x = [0] * (800 * 1024 * 1024); import time; time.sleep(120)",然后观察vmstat的si/so变化。
3.3 案例三:磁盘I/O拖后腿,业务大面积超时
现场:数据库查询变慢,大量SQL执行时间从正常几十毫秒暴涨到几秒。
操作:连续采样vmstat:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 1 4 0 345678 120345 5234567 0 0 89000 12000 9000 30000 10 12 10 68 0解读:b列变成4,说明有进程阻塞在I/O等待上;wa高达68%;bi达到89MB/s(注意单位以你的版本为准)。三列组合在一起,基本坐实是读I/O瓶颈。这种输出下,CPU大部分时间在"干等"磁盘,应用自然会超时。
结论与动作:确定是I/O瓶颈后,立刻跑vmstat -d或iostat -x看具体是哪个磁盘、哪块分区最忙,再用iotop或pidstat -d找到罪魁祸首。常见原因包括:SQL全表扫描、日志量突然暴增、数据库未命中大量执行排序操作、磁盘本身硬件老化。解决办法通常是优化SQL、增加缓存、把临时表空间挪到SSD,实在不行再考虑升级磁盘设备。
3.4 案例四:云主机诡异的"慢":CPU全额付费,性能打折
现场:云上的一台8核实例,业务量没变化,但应用接口耗时涨了一倍。进系统看top,CPU使用率不高,负载也不高,就是感觉慢。
操作:执行vmstat -t 1 5,重点看st列:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 1 0 0 567890 230456 6123456 0 0 12 25 8000 22000 8 4 72 0 16解读:id还有72,说明CPU大部分空闲,但st高达16%,也就是说我们CPU的16%时间被宿主机抢走了。这是因为同一台物理机上其他邻居虚拟机在忙,大家共享的物理CPU资源被挤占,我们的性能自然缩水。这不属于操作系统层面能解决的问题。
结论与动作:跟云服务商确认宿主机负载情况,必要时把实例迁移到另一台物理机,或者直接升级成有独享CPU约束的规格。再说细一点,如果st长期超过10%,业务型实例就该考虑换规格了;跑批任务趁深夜低峰期,影响还能小些。
4. 进阶用法与踩坑记录
4.1 采样间隔怎么选才科学
我见过不少同事采样时凭习惯乱填:要么只跑一次vmstat,看到的是转瞬即逝的瞬时值;要么vmstat 5挂着不管,结果峰值被5秒平均抹平了。
我的经验是分场景选择。应急排查用vmstat 1 10:1秒间隔能看到瞬时波动,10个样本点基本能覆盖一次抖动。如果是长时间观察趋势,比如要看业务高峰期两小时内的内存走向,建议用脚本每30秒或60秒记录一行,而不是让vmstat一直刷屏。短间隔适合捕捉抖动成因,长间隔适合画趋势图,这个道理跟监控系统的采样粒度一模一样。
还有个小技巧:想把vmstat输出直接落盘,可以用vmstat 1 100 > /tmp/vmstat_$(date +%s).log,日志文件后面随时可以回看。如果担心磁盘占满,记得轮转或限定count参数。
4.2 用-s和-d把信息看得更细
vmstat -s是内存统计的一站式汇总,输出里包含total memory、used memory、active memory、inactive memory、free memory、buffer cache、swap cache等一长串数值。我的习惯是排查内存问题时先敲这一条,比free -m的信息量更大,能看到active和inactive的具体分布,有助于判断缓存回收是否健康。
vmstat -d则是磁盘维度的扩展,输出每个物理磁盘的reads、writes、merged reads/writes、IO队列等指标。当bi/bo或wa异常时,用vmstat -d能直接锁定是sda还是sdb在忙,省得再装iostat。比如输出里某个磁盘的reads持续增长、IO队列数值很大,就说明压力集中在这块盘上。
4.3 用awk提取指定列做趋势观察
有时候连续采样的信息太多,人眼盯不过来。我的做法是用awk只提取关心的列。比如只想看context switch(第12列,cs)的变化:
vmstat 1 10 | awk 'NR>2 {print strftime("%H:%M:%S"), $12}'这里NR>2跳过表头,$12对应cs列。如果想同时提取CPU的wa列(第16列):
vmstat 1 10 | awk 'NR>2 {print $1, $12, $13, $14, $15, $16}'注意awk按行分隔,列序号务必对照好。如果你用-w宽屏模式,列宽变了,awk的列位置和默认模式是一致的,所以加-w不影响提取。
我建议把这些采样脚本放在/usr/local/bin下,遇到问题直接调用,省得每次现场手敲。
4.4 我在实操中踩过的坑
首先是大坑:在部分Linux发行版中,cache列在新内核里的计算口径跟老内核不完全一样。老一点的版本cache包含tmpfs占用的内存,新版本把tmpfs单独拎出去了,导致同样的内存使用状态,不同内核版本显示的数字可能差不少。遇到数字对不上时,先看内核版本再下结论。
其次是容器环境的问题:如果你在Docker容器内执行vmstat,看到的部分字段可能是宿主机的,尤其是/proc/stat和/proc/meminfo相关的数据。容器里看到的free可能是宿主机全局内存,不是容器配额内存。排查容器内存问题应该用cgroup数据(cat /sys/fs/cgroup/memory/memory.usage_in_bytes),别盲目用vmstat判断容器的内存水位。
还有一个使用习惯问题:不要拿vmstat单次输出做决策。单次输出跟抽签差不多,系统瞬时波动会给你错误信号。正确做法是连续采样至少5次,看趋势和稳定值,再做判断。
最后是单位问题:-S M只影响内存相关列(swpd/free/buff/cache等),不影响si/so和bi/bo这些速率列,也不影响io的块计数。我见过有人用了-S M后以为si/so也变成MB/s了,结果数值量级完全对不上。这块务必看man手册确认。
5. 常被误读的指标与排查速查表
5.1 三句话总结核心判断逻辑
我把最常用的判断规则浓缩成三句话,平时排障照着这个思路走,方向就不会错:
- CPU瓶颈:r值大于CPU逻辑核数,us或sy高,id低。如果是us高,问题在应用代码;如果是sy高,重点查系统调用、中断、锁竞争。
- 内存瓶颈:si/so持续非零,swpd还在增长,free逼近于无。此时系统正在做换页,性能必然受损。
- I/O瓶颈:wa高、b列非零、bi/bo明显放大。再看vmstat -d或iostat确认具体磁盘,用iotop定位进程。
5.2 判断阈值的经验参考
下表是我在真实运维中习惯参考的经验阈值,注意这不是绝对标准,不同机器、不同业务、硬件差异都会让数值有波动,它们的作用是帮你快速建立"正常/异常"的直觉:
| 指标 | 正常参考 | 需要关注 | 紧急处理 |
|---|---|---|---|
| r | 小于核数 | 接近或略大于核数 | 持续大于核数1.5倍以上 |
| us+sy | 60%以下 | 80%左右 | 持续90%以上 |
| wa | 5%以下 | 20%左右 | 持续40%以上 |
| si/so | 长期为0 | 偶尔非零 | 持续非零且值较大 |
| cs | 小于1万 | 3万到5万 | 持续10万以上 |
| st(云主机) | 0到3% | 5%到10% | 持续超过15% |
给新手一个建议:先在自己负责的机器上记录一套"正常业务流量下的vmstat基准值",比如每天中午采样一分钟,坚持两周你就有这台机器的"生活习惯"。之后任何异常变化,都逃不过你的眼睛。这个方法比死记阈值实用太多。
5.3 常见的误读场景
free小等于内存不足?不对。Linux内核用缓存策略吃满空闲内存,大量cache是常态。判断内存是否真不足,一定要看si/so。
wa高说明磁盘坏了?也不完全对。wa高只能说明CPU在等I/O,可能是磁盘慢、可能是I/O队列太长、也可能是某进程在疯狂读写。先用vmstat -d和iotop缩小范围,再考虑硬件问题。
r值大就说明CPU差?也不准确。如果sy和cs也很高,有可能是系统调度抖动或锁竞争导致的,虽然最后都会表现为CPU忙,但解决方向完全不同。能区分us和sy的意义就在这里。
5.4 老手自查清单
如果是老手,遇到性能问题不想从头看,可以按这个清单走一遍vmstat视角的快速自查:
- 先
vmstat -t -w 1 10采样10秒 - 第一眼扫r和id:r接近或超过核数、id接近0,CPU瓶颈
- 第二眼扫wa和b:wa大于20%且b非零,I/O瓶颈
- 第三眼扫si和so:持续非零,内存瓶颈
- 第四眼扫cs:数值异常高,检查线程数量和锁竞争
- 云主机额外扫st:大于10%,考虑宿主挤占
这套流程30秒内就能跑完,接下来才需要用pidstat、iotop、perf等工具深入。我用这个流程处理过很多次生产故障,不敢说百发百中,但至少能快速排除掉80%的方向性误判。
最后分享一点个人体会:vmstat这个命令看起来简单,但真正用好的人其实不多,多数人只记住了几个字段名,真到了故障现场还是会慌。我建议你找个测试机,人为制造CPU、内存、I/O压力,然后反复用vmstat观察输出变化,两周后你再看任何性能问题都会有种"有把握"的感觉。工具是死的,但解读数字的思路可以练成熟练度。后面等你看熟了,可以把vmstat和sar、iostat、pidstat的联动配合也练一练,这套组合基本覆盖了日常运维90%以上的性能诊断场景。