有一次线上订单库突然变慢,CPU、内存、网络都看不出异常,可单条查询就是卡得让人抓狂。折腾了半天,最后是iotop一锤定音——磁盘I/O被一个进程打满了。从那以后,iotop就成了我排查系统卡顿时必看的第一梯队命令,没有之一。
如果你也遇到过类似情况:明明iostat显示磁盘忙得快炸了,top里却找不出是哪个进程在作妖,那这篇内容很适合你。iotop定位的问题很具体——实时监控每个进程的磁盘I/O读写速度,精确到每个线程。不管你是做运维、后端开发、数据库管理,还是刚入行学Linux常用命令的初学者,只要是靠Linux吃饭,都值得把这个工具用熟。这算是Linux命令大全里被低估的一员,但它在实战中的价值远超很多人的预期。
1. top和iostat看不到的那层信息:iotop到底在监控什么
1.1 iotop的定位:把“磁盘忙”拆解到“进程忙”
先理清楚一个概念:top能看到CPU、内存,也能看到wa(I/O wait)参数,但wa高只能告诉你“有进程在等I/O”,等的是谁、谁发起的I/O、每秒读写了多少,top一概不显示。遇到I/O引发的性能问题,只看top往往要猜半天。
iostat倒是能看到磁盘级别的数据,比如util使用率、吞吐量、IOPS、平均队列长度。它能告诉你“磁盘是不是已经忙到头了”,但同样回答不了“是哪个进程把它干爆的”。
iotop就是来补这个空白的。它的名字很直白——IO版的top,它以近似top的交互界面,实时列出系统里每个进程/线程的磁盘读写速率、I/O等待百分比、I/O优先级。一句话总结:iostat负责告诉你磁盘有多忙,iotop负责告诉你到底是谁让磁盘这么忙。
从排查链路上讲,我习惯的顺序是:先free、top看内存和CPU,再用iostat -x 1确认磁盘是瓶颈,最后打开iotop去抓元凶。这个顺序能帮你少走很多弯路。
1.2 和iostat、pidstat放在一起看,职责才算清晰
很多新人容易把这类工具混着用,其实它们的定位差异非常明显。
| 工具 | 回答的问题 | 关键输出 | 适用场景 |
|---|---|---|---|
| top | 系统整体负载、CPU、内存快照 | CPU使用率、LOAD、内存 | 第一时间的全局视图 |
| iostat | 某块磁盘有多忙、吞吐多大 | util、await、svctm、tps | 确认磁盘是否是瓶颈 |
| pidstat -d | 指定进程的I/O统计 | KB_READ、KB_WRKT | 已经知道进程范围,想做周期性统计 |
| iotop | 全系统哪些进程/线程正在读写磁盘 | DISK READ、DISK WRITE、IO> | 定位“谁在疯狂写盘” |
所以iotop和其它工具并不互斥。实际工作中,我们往往先用iostat判断磁盘压力,然后用iotop缩小嫌疑范围,再用lsof、strace之类去定位具体文件或调用。iotop在中间这一环几乎是不可替代的——没有它,你只能把所有可疑进程挨个加到pidstat里盯,效率低得让人崩溃。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 安装、权限与基础参数:非root跑出的Permission denied是怎么回事
2.1 三个发行版系的安装命令
iotop很轻量,依赖很少,大部分源里都有。不同发行版安装方式略有差别,这个要注意,别复制错指令。
bash复制# Debian / Ubuntu 系
sudo apt-get install -y iotop
# CentOS 7 / RHEL 7 及兼容版本
sudo yum install -y iotop
# Fedora / RHEL 8及以上
sudo dnf install -y iotop
如果你的CentOS是最小化安装,有时候默认源里没有,需要先装epel-release,再执行yum install iotop。装完验证一下:
bash复制iotop --version
我遇到过一些机器,系统装了很多年,iotop已经预装在里面,但Shell里直接敲会报一堆Permission denied,很多人这时候就以为是命令坏了,其实不是。
2.2 非root用户为什么必须加sudo
这里值得展开讲一下,因为涉及iotop的原理。iotop查看进程I/O,数据来源是每个进程的/proc/
text复制Could not open /proc/3120/io: Permission denied
更烦人的是,iotop本身不会直接崩,它会继续显示,但别人的进程全部变成只有进程名、没有读写数字,跟自己相关的进程才能看到数据。这种半残状态很容易误导排查方向,所以记住一句话:跑iotop,直接上sudo,不要犹豫。
除了root权限,某些场景还需要CAP_NET_ADMIN之类的权能,但说实话日常用sudo就够了。如果你在公司机器上,sudo都要审批,那建议让有权限的同事帮忙,或者申请批量巡检用的只读权限。
2.3 一组好用的启动参数和它们真正的作用
iotop不传参数也能直接跑,但想让它更贴合场景,下面几个参数是我平时最常用的。
| 参数 | 作用 | 实战说明 |
|---|---|---|
| -o | 只显示有I/O行为的进程/线程 | 打开瞬间就过滤掉大量0读写进程,画面干净太多 |
| -b | 非交互模式,用于脚本采集 | 数据会不断滚动输出而不是刷新全屏 |
| -n NUM | 配合-b,指定采集次数 | 不写的话会一直跑,脚本里容易失控 |
| -d SEC | 刷新间隔,默认1秒 | 调成3或5,数据更稳定,I/O抖动不容易误判 |
| -k | 以KB为单位显示 | 默认是自适应单位,脚本解析时很不方便 |
| -t | 打印时间戳 | 记录日志时必备 |
| -P | 只看进程不细分线程 | 线程太多时先聚合成进程视角 |
| -a | 累计模式,显示累计读写量 | 用来判断某进程过去一段时间到底读写了多少 |
| -u USER | 只看指定用户的进程 | 多租户机器上很好用 |
| -p PID | 只监控指定PID | 定位某个进程时用它最准 |
举个实际命令:
bash复制sudo iotop -o -d 3 -k
这条命令的意思:只显示有I/O的进程,每3秒刷新一次,读写量用KB展示。打开之后画面清爽,不会满屏都是0。要是某天线上有问题,我基本就是这么开着挂一会儿,几轮刷新之内就能看出谁在作妖。
3. 界面上的每一列:有人把IO>超过100%当成故障,其实不是
3.1 TID、PRIO、USER这些“身份列”
iotop的默认界面分为两大部分:上半部分是系统磁盘和线程/进程的总览(TOTAL读写速率、磁盘利用率等),下半部分就是动态刷新的进程/线程列表。
看列表先看前三列:
- TID:内核线程ID,不是传统意义上的PID。注意,iotop默认显示到线程粒度,你没看错,一个多线程进程会拆成好多行。所以看到几十行Nginx的worker线程并列,别慌,这是正常的。
- PRIO:I/O优先级。这里显示的是内核I/O调度器能识别的优先级,和CPU优先级(NI值)不是一回事。可以用ionice命令调整,比如把备份任务的I/O优先级调低,降低它对线上业务的影响就是靠它。
- USER:进程属主,结合-u参数可以快速按用户过滤。
3.2 DISK READ / DISK WRITE的语义
接下来的DISK READ和DISK WRITE是核心数据列,单位默认根据数值自动切换(B/K/M/G),如果加了-k参数就固定显示为KB。
这两列指的是进程每秒实际从块设备读取/写入的数据量,不是应用代码里调用read()/write()的字节数。关键区别在于Page Cache:应用写数据往往先写进内存缓存,真正回写到磁盘才会计入DISK WRITE;读数据如果命中缓存,也不会体现在DISK READ上。所以有时候你会发现,应用明明在大量写日志,但iotop里DISK WRITE并不大,因为数据还在内存里没落盘。
理解这个语义很重要,否则你会觉得“为什么日志在刷但iotop没显示”,会白白浪费时间。
3.3 SWAPIN和IO>:两个“等待百分比”不能混为一谈
SWAPIN和IO>是新手最容易看懵的两列,我见过有人把IO>超过100%当成磁盘故障拿去报警,其实是理解偏差。
- SWAPIN:线程等待内存换入(swap in)的耗时百分比。这个值如果长期偏高,说明系统内存压力很大,进程在频繁把页面从交换分区读回来。注意,这不是磁盘I/O主要指标的范畴,它是内存不足的旁证。
- IO>:线程等待I/O完成的时间百分比。它衡量的是线程有多少时间花在了等磁盘上,而不是磁盘本身有多忙。如果IO>长期压在50%以上,说明该进程大量时间被I/O卡住,要么I/O太慢,要么请求太密。
IO>超过100%不是故障,别看到数字破百就以为bug了。多线程场景下,一个进程同一时间段可以并发发起多个I/O请求,等待时间会叠加,所以200%、300%都出现过。尤其在NVMe固态盘、多队列块设备上,高并发等待很容易导致数值虚高。判断异常不要只看绝对值,要和DISK WRITE等指标一起看。
3.4 用累计模式观察长时间趋势
默认模式是实时速率,适合抓“正在发生的异常”。如果我想知道“过去一个小时内,哪个进程累计写了最多数据”,就按一下a键进入累计模式,DISK READ和DISK WRITE会变成自进程启动以来的累计字节数,用-K或自适应单位显示。
累计模式在压测的时候特别有用。比如白天跑了一轮压测,压测结束进程还活着,我可以切到累计模式看看压测进程总共产生了多少I/O,再除以时间,估算平均I/O压力。这个数据比瞬时值更能反映整体负载。
4. 交互模式速成:几十秒内锁定疯狂写盘的线程
4.1 排序、过滤、进程切换的快捷键
iotop打开后是交互界面,快捷键不算多,但每一个都很关键。
| 快捷键 | 作用 |
|---|---|
| 左右方向键 / <> | 切换排序字段(DISK READ、DISK WRITE等) |
| r | 反转排序顺序(升序/降序切换) |
| o | 只显示有I/O活动的进程,再按一次恢复 |
| p | 在“只显示进程”和“显示所有线程”之间切换 |
| a | 切换实时速率/累计模式 |
| q | 退出 |
排序和过滤配合起来,效果就很直观。我经常先按o过滤,再通过左右方向键切到DISK WRITE列,按r反向排序,让写盘量最大的排在最上面,一眼就能看到元凶。
4.2 现场两分钟定位法
给你还原一个我常用的操作流程,拿多线程Java应用举例。
假设现在磁盘iostat看起来已经接近100%,但CPU很空闲,打开iotop,第一步先按p切换到进程视角,因为线程太多时,一个Java进程可能占了十几行,I/O分散开以后反而不容易看出总量。
接着按o,只留下有I/O的进程。再按右方向键两次,把排序列切到DISK WRITE,按r让最大值排到第一行。这时候往往就能看到某Java进程的DISK WRITE一直在跳动,IO>也居高不下。如果再按a看累计模式,还能确认它在过去一段时间里的写盘总量。
之后如果还需要进一步定位它写了哪些文件,就可以另开窗口用lsof、strace去查那个进程。iotop在整个过程中扮演的角色是“快速锁定嫌疑人”,而不是“最终定罪”。
再说一个细节:如果看到大部分I/O集中在某个root用户的进程,且命令名是[jbd2/sda1-8]这种带方括号的,那是内核日志进程,不是普通的应用进程,点了也没日志可查。这种情况我把iotop关掉,去检查磁盘分区是不是有问题,或者是不是有应用在频繁触发日志元数据更新,那就是另一条排查路线了。
5. 批处理与脚本化:iotop不是给你盯着看的,它是采集器
5.1 非交互模式的参数组合
交互模式适合人坐在电脑前抓问题,但真实生产环境里,很多I/O异常发生在半夜,这时候就需要把iotop变成“无人值守的采样器”。
非交互模式的核心是-b参数。打开-b后,iotop不再刷新全屏,而是像普通命令一样把结果一行一行输出,配合-n和-d就能控制采几次、隔多久采一次。
我常用的三种组合:
bash复制# 每2秒采样一次,总共5次,只输出有I/O的进程
sudo iotop -b -n 5 -d 2 -o
# 不限制次数,每10秒采样一次,同时打印时间戳,适合后台挂机
sudo nohup iotop -b -d 10 -o -t -k >> /var/log/iotop.log 2>&1 &
# 只看某个指定进程,每5秒刷新一次
sudo iotop -b -d 5 -p 12345
第一种适合手工临时抓数据,第三种适合后台盯某个可疑进程,第二种适合长时采集,数据全量落盘,事后回溯。
5.2 巡检脚本实践
我自己一般会把iotop写进巡检脚本,每隔几分钟采样一小段。脚本大致长这样:
bash复制#!/bin/bash
# iotop巡检采集脚本
INTERVAL=5
LOG_DIR=/var/log/io_monitor
mkdir -p "$LOG_DIR"
while true; do
echo "================ $(date '+%Y-%m-%d %H:%M:%S') ================" >> "$LOG_DIR/iotop.log"
sudo iotop -b -n 1 -d $INTERVAL -o -k -t >> "$LOG_DIR/iotop.log" 2>&1
sleep 30
done
注意这里的逻辑:iotop -b -n 1 -d 5表示只采样一轮,内部统计时长5秒。命令跑完退出,外层sleep 30再循环,所以实际大概35秒左右生成一次采样。如果你希望采样更连续,可以把sleep去掉,直接让iotop持续跑,反正-o已经过滤了无I/O进程,日志不会太爆炸。
5.3 采集完怎么分析
日志文件攒一天以后,想快速看某段时间内写盘最猛的进程,可以用awk直接抽DISK WRITE列。
bash复制awk '/^[0-9]/ && $5 != "0.00" {print $2, $4, $5}' /var/log/io_monitor/iotop.log | sort -k3 -rn | head -20
不同版本的iotop输出列顺序略有不同,建议先head一页日志确认字段位置再写awk,不要直接照抄。还可以配合grep命令名来统计某个进程出现的次数,判断它的I/O是不是长时间持续性的。
这里的价值在于:批量任务、夜间备份、日志采集这类I/O异常不一定会当场被人发现,但只要有iotop的历史日志,事后回溯就很轻松。排查问题从“不知道什么时候开始”变成“直接看日志找起点”,效率提升一个档次。
6. 排障实录:一次AOF重写引发的写盘风暴
6.1 故障现场:iostat已经告诉我磁盘快满了,问题是“谁”
那次我负责的Redis实例一到晚上8点批量任务时段就变慢,慢查询偶发变多。第一反应看CPU和内存,都正常。然后用iostat -x 1看磁盘,发现sda的util直接顶到99%,avgqu-sz也在高位跳动,磁盘确实忙疯了。
问题来了:磁盘忙,是谁在忙?
top里看不到任何进程CPU飙高,因为Redis写磁盘是后台异步的,CPU占用率甚至看不出明显波动。如果没有iotop,我可能要在所有进程里挨个试错,那晚别想休息了。
6.2 iotop锁定redis-server的子进程
打开iotop,按照前面说的方法按o过滤,再切到DISK WRITE排序,最上面一行赫然是redis-server,DISK WRITE稳定在20到40MB/s之间,IO>经常跳到100%以上,有时候直接顶到400%。
但我仔细一看,这个redis-server的进程结构不太对。主Redis进程是常驻的,怎么会有另一个同样叫redis-server的进程也在写盘?查一下父子关系:
bash复制ps -ef | grep redis-server
cat /proc/<可疑PID>/status | grep PPid
结果明确显示,这是一个子进程,父进程就是Redis主进程。再往深挖,用lsof看这个子进程打开的文件:
bash复制sudo ls -l /proc/<可疑PID>/fd | grep -E 'aof|temp'
发现它正在写一个类似temp-rewriteaof-xxxx.aof的临时文件。到这里,真相已经水落石出——这是Redis触发了AOF后台重写(AOF rewrite),子进程在重新生成AOF文件,把当前内存里的全量数据快照落盘。
6.3 为什么只看进程名会误判
如果只凭iotop里“redis-server”这个名字就下结论,很容易犯错误:以为是主进程在做持久化,进而得出“Redis自身有问题”的错误判断,然后把方案引向重启、迁移这种大动作。
但实际上,Redis主进程还活得好好的,是它的重写子进程在I/O上抢资源。这个误判的根源在于,AOF rewrite的子进程和主进程有相同的进程名,容易让人忽略它是单独的子进程。
所以排查I/O问题时,看到进程名一样别急着收工,用ps或/proc确认一下父子关系和线程归属,往往能避免方向性错误。
6.4 顺着线索找到配置根因
锁定了“Redis AOF重写”以后,剩下的问题就是:为什么它会在这个时间点触发重写?
Redis的AOF重写触发条件由两个配置控制:
- auto-aof-rewrite-percentage:默认100,表示AOF文件比上次重写时增长了100%才触发重写。
- auto-aof-rewrite-min-size:默认64mb,AOF文件小于这个值不触发重写。
晚上批量任务导致写量剧增,AOF文件短时间膨胀,很快超过上次重写大小的两倍,自动重写被触发。而主库在重写期间不仅子进程在写磁盘,主进程自身还在写AOF缓冲,两头叠加,磁盘直接被打到满负荷。
解决的思路有几条:
- 把auto-aof-rewrite-percentage调大,减少重写触发频率,比如调到200或300。
- 把批量任务的写入速率平滑一下,不要集中在同一时间段。
- 如果对数据安全要求不算极端,可以开启no-appendfsync-on-rewrite yes,让重写期间主进程跳过fsync,降低磁盘压力。
最终我们选了方案一加上方案二配合,问题解决。
提示:这次排查让我对iotop的定位理解得更透彻——它帮你锁定了“谁”,但“为什么是它”还要靠对业务和中间件运行机制的理解去补完。
7. 容器、内核线程与采样误差:iotop绕不开的几个坑
7.1 容器里执行iotop,看到的是宿主机还是容器进程?
很多人把iotop装进Docker容器,想监控容器内的I/O,然后发现画面里全是宿主机上其它进程,瞬间懵了。
由于容器共享宿主机的内核,/proc目录里能看到所有进程的I/O统计,iotop默认展示全宿主机的进程。你在容器里看到的,不是属于你容器的隔离视图,而是宿主机全局视图。这既是bug也是feature——如果你在宿主机上追一个容器进程,反而更好办,直接在宿主机跑iotop -p <容器内进程在宿主机的PID>即可。
如果你想获得某个容器的I/O整体状况,更靠谱的做法是用cgroup接口,或者直接用docker stats查看Block I/O。理解这一点,能省去很多检修时间。
7.2 内核线程的I/O“查无此人”
用iotop时会发现有些进程名是方括号括起来的,比如:
- [jbd2/sda1-8]:文件系统日志线程
- [md0_raid1]:软raid同步线程
- [kworker/u8:0]:内核工作线程
这些是内核线程。它们的I/O活动不是由某个用户态进程发起的,比如文件系统日志的写入,可能来自无数个应用进程的间接触发,但归因的时候只能归到jbd2头上。
遇到这种情况,iotop只能告诉你“内核层在写盘”,但具体是哪个用户态应用逼着内核写日志、刷脏页,需要配合vmstat看si/so、procs的D状态进程,或者用更底层的追踪工具(比如bcc工具集)去深挖。知道iotop的边界在哪里,比什么都会一点更重要。
7.3 IO wait不高不代表磁盘不忙
top里的wa不高,是不是就能排除磁盘问题?还真不能。
现代服务器CPU核数多,wa是一个全局平均的CPU等待占比,某一块盘的I/O压力可能被多核摊薄到很低。加上很多I/O等待发生在中断、D状态进程里,不一定会座实CPU的wa上。
我见过一台64核的机器,iostat显示某块盘util到了98%,top里wa才2%。如果只盯着top,你根本想不到瓶颈在磁盘。所以判断磁盘是否繁忙,最直接的是iostat -x的util和await,而不是top的wa。
7.4 采样间隔与瞬时I/O
iotop默认每秒刷新一次,它会读取两次/proc统计值,算出差值作为速率。这意味着如果一个I/O脉冲持续不到1秒,采样可能只能捕捉到一个开头,甚至完全错过。遇到间歇性抖动,可以把-d调小到0.5秒试试,但代价是iotop自身占用的CPU会稍微上涨,同时频繁读取/proc对所有进程都有一点影响。
反过来,如果你想减少抖动、看更稳定的均值,就调大间隔。一个经验值:日常巡检用3秒,故障抓现行用1秒,精细化分析用0.5秒。
另外要注意,iotop显示的是速率,受采样窗口影响很大。有时候你盯着一个进程的DISK WRITE,数值一会儿0一会儿20MB/s,并不代表它真的在疯狂抖动,可能只是那1秒内落盘的归因不平均。看趋势更容易得出正确结论。
最后分享一个使用习惯
排查过太多次“系统莫名变慢”以后,我最终的体会是:iotop本身不难,难的是把它放进一整套判断链路里。我的固定套路是top看全局、iostat确认磁盘、iotop锁定进程、lsof和strace深挖文件与调用,每一步都有明确目的,不回头,不猜。
如果你刚上手,我建议先从最简单的命令开始:sudo iotop -o -d 3 -k,开一次,盯着刷新几轮,看看哪些进程平时在做I/O。跑一个星期以后,你会对这台机器的“正常I/O底噪”形成感觉,以后哪天数据异常,一眼就能看出来。
还有个小技巧:把常用组合写进Shell的历史命令里,早晚会用得上。我自己的history里存着一条长年未删的命令:
bash复制sudo iotop -b -n 30 -d 2 -o -k -t > /tmp/iotop_$(date +%F_%H%M).log
临时要用的时候直接调出来,半小时的I/O采样数据自动落盘,拿来分析批量任务或者给报告配图都好使。工具就是这样,用熟了,它就是你肚子里的蛔虫。
