工业边缘设备出现卡顿时,I/O 往往是重要线索:日志刷盘、离线数据补传、数据库 compaction、容器镜像构建、备份任务,都可能让 eMMC、SD 卡或 SSD 出现突发压力。iotop 能回答"哪个进程或线程正在产生块设备 I/O",但它不能单独回答"设备是否饱和、延迟来自哪里、文件是哪一个、容器是否被限流"。
本文按工程排查顺序整理 iotop 的使用方法、字段口径、I/O 优先级边界,以及与 iostat、pidstat、blktrace、cgroup 的配合方式。
一、先建立排查框架
| 层次 | 关注问题 | 常用工具 | 能回答 |
|---|---|---|---|
| 块设备层 | 吞吐、延迟、队列、繁忙程度 | iostat |
/dev/sda、/dev/nvme0n1 是否异常 |
| 进程 / 线程层 | 谁在读写在做什么 | iotop、pidstat |
哪个进程或线程贡献主要 I/O |
| 系统与文件层 | 缓存、脏页、容量、文件句柄 | df、du、/proc/meminfo、lsof |
是否为空间、回写或已删除文件未释放 |
| 块层细节 | 排队、调度、设备完成耗时 | blktrace、blkparse、btt |
延迟主要花在排队还是设备完成 |
| 容器层 | 容器级 I/O 与限流 | cgroup io.stat / io.max |
容器用量是否异常或被限制 |
不要把 iotop 当唯一入口。一个常见误判是:iotop 中没有明显业务进程,但 iostat 显示设备很忙,这时要考虑脏页回写线程、文件系统日志线程、备份任务、虚拟设备聚合,或容器 PID 命名空间带来的视角偏差。
二、iotop 的运行前提
iotop 依赖 Linux taskstats / task I/O accounting 能力。部分发行版的较新内核默认没有开启 delay accounting,会导致 SWAPIN、IO 等字段不可用或不准确:
bash
# 查看当前设置
sysctl kernel.task_delayacct
# 临时开启,重启后失效
sudo sysctl kernel.task_delayacct=1
若要长期开启,应通过发行版支持的内核参数或 sysctl 配置管理,并先在测试环境验证。iotop 通常需要 root 权限;通过 capability 授权时要遵循最小权限原则,不要给普通服务进程随意放大能力。
三、常用命令
bash
# 交互模式:只显示有 I/O 的任务,并按进程聚合线程
sudo iotop -o -P -d 2
# 批处理模式:每 2 秒采样一次,共 10 次,输出时间和 KB
sudo iotop -b -o -P -t -k -n 10 -d 2 | tee iotop.log
# 累计 I/O
sudo iotop -a -o -P -d 2
# 指定用户
sudo iotop -u myapp -P -d 2
# 指定一个 PID
sudo iotop -p 12345 -P -d 2
# 指定多个 PID
sudo iotop -p 12345 -p 12346 -d 2
参数口径:
| 参数 | 含义 | 工程注意点 |
|---|---|---|
-o |
只显示当前有 I/O 的任务 | 排障时通常先加 |
-P |
按进程聚合线程 | 查线程热点时去掉 -P |
-a |
显示累计 I/O | 是任务自身累计值,不是当前采样周期内的速率 |
-b |
批处理模式 | 适合采集落盘和离线分析 |
-d |
采样间隔 | 默认 1 秒,突发负载建议观察多个周期 |
-n |
采样次数 | 与 -b 配合使用 |
-t |
输出时间戳 | 便于和应用日志、iostat 对齐 |
-k |
使用 KB 单位 | 输出更适合脚本和人工比对 |
-p |
指定 PID | 可重复指定 |
-u |
指定用户 | 常用于按服务账号隔离 |
特别注意:iotop -a 展示的是任务累计 I/O,不是当前速率。判断瞬时压力应看默认速率模式;判断长时间任务贡献量时,累计模式才有意义。
四、字段怎么读
| 字段 | 含义 | 工程判断 |
|---|---|---|
PID |
进程或线程 ID | 默认可能显示线程,-P 才按进程聚合 |
PRIO |
I/O 调度优先级 | 来自 I/O scheduler / ionice,不是 cgroup 带宽限制 |
USER |
任务所属用户 | 可用于快速识别服务账号 |
DISK READ |
任务读取速率或累计读取量 | 受采样口径和内核统计能力影响 |
DISK WRITE |
任务写入速率或累计写入量 | 写入可能被 page cache 延后 |
SWAPIN |
任务换入等待比例 | 明显偏高时先排查内存压力 |
IO |
任务等待 I/O 的时间比例 | 与设备延迟和队列一起看 |
COMMAND |
命令行 | 需要继续确认文件路径和业务语义 |
iotop 的进程读写量是任务与块设备子系统之间的统计,不等于应用调用 write() 的字节数。应用写入可能先进入 page cache,真正的磁盘回写会延后发生;请求合并、日志文件系统、RAID、overlay 和远程存储也会改变最终落盘行为。
如果只看到 kworker、jbd2、数据库后台进程或容器运行时活跃,不要直接下结论,应继续查脏页、文件系统日志、备份任务、镜像构建和 cgroup。
五、I/O 优先级:ionice 不是带宽限制
ionice 用于设置 I/O 调度类别或优先级,不能提供确定性带宽上限,也不能替代 cgroup I/O controller。其效果还取决于内核和设备使用的 I/O scheduler;一些 NVMe 场景使用 none 或 mq-deadline,ionice 的可感知效果可能有限。
bash
# 查看已有进程的 I/O 优先级
ionice -p 12345
# 将运行中的任务改为 best-effort 7,对其他任务更友好
sudo ionice -c 2 -n 7 -p 12345
# 将运行中的任务改为 idle,仅在没有其他 I/O 时服务
sudo ionice -c 3 -p 12345
# 以 best-effort 7 启动任务
ionice -c 2 -n 7 ./myapp
| 类别 | 名称 | 含义 | 使用建议 |
|---|---|---|---|
1 |
realtime | 尽量优先处理 | 谨慎使用,可能让其他任务长期等待 |
2 |
best-effort | 默认类别,带 0 到 7 级优先级 | 常用于降级备份、同步和批处理 |
3 |
idle | 空闲时处理 | 适合旁路任务,但依赖调度器支持 |
如果需要明确的读写带宽或 IOPS 限制,应使用 cgroup v2 的 io.max、存储 QoS,或从应用层降低批量大小和并发。
六、完整排障流程
1. 确认设备层是否异常
bash
iostat -xz 2 5
第一份报告通常是系统启动以来的平均值,后续报告才是近实时采样。重点记录:
r/s、w/s:每秒读 / 写请求数;rkB/s、wkB/s:每秒读 / 写数据量;await、r_await、w_await:平均 I/O 等待与服务耗时;aqu-sz:平均队列长度;%util:设备有 I/O 请求的时间比例。
%util 对并行 SSD / NVMe 不能单独作为饱和结论;它接近 100% 不一定代表设备耗尽,反过来低 %util 也不排除个别请求延迟很高。判断饱和要结合队列长度、延迟、吞吐、IOPS、设备类型和应用响应时间。
2. 用 iotop 定位主要任务
bash
sudo iotop -b -o -P -t -k -n 5 -d 2 | tee iotop.log
如果业务进程不明显,但系统写入量高,继续观察:
bash
watch -n 1 'grep -E "^(Dirty|Writeback):" /proc/meminfo'
Dirty 持续抬升后出现写爆发,通常说明脏页回写或批量任务造成周期性抖动。此时要结合 vm.dirty_ratio、vm.dirty_background_ratio、应用批量写入策略和设备写入能力评估。
3. 找文件与容量证据
bash
df -hT
du -xhd 1 /var | sort -h
find /var/log -xdev -type f -printf '%s %p\n' | sort -nr | head -n 20
journalctl --disk-usage
sudo lsof +L1 | head -n 30
du 加 -x 可以避免跨文件系统统计。lsof +L1 用于发现已被删除但仍被进程打开的文件:这类文件在 df 中仍占空间,但按目录执行 du 时可能找不到。
4. 容器场景先看 cgroup
容器主进程 PID 不等于容器内所有进程和线程。先确认进程树:
bash
CONTAINER_PID=$(docker inspect --format '{{.State.Pid}}' mycontainer)
pstree -ap "$CONTAINER_PID"
cgroup v2 下,更稳妥的是直接查看容器 cgroup 的 I/O 统计和限制:
bash
CID=$(docker inspect --format '{{.Id}}' mycontainer)
CGROUP=$(find /sys/fs/cgroup -type d -name "docker-${CID}.scope" -print -quit)
cat "$CGROUP/io.stat"
cat "$CGROUP/io.max"
io.stat 常见字段包括 rbytes、wbytes、rios、wios,具体依赖内核版本;io.max 可以确认是否配置了读写带宽或 IOPS 限制。Kubernetes、containerd、podman 的 cgroup 路径不同,但思路一致:先定位 cgroup,再看统计与限制。
七、配合工具怎么用
pidstat:更适合长期采集
iotop 适合现场交互定位,pidstat 更适合周期采集和趋势分析:
bash
# 每 2 秒输出一次,共 5 次
pidstat -d 2 5
# 指定进程
pidstat -d -p 12345 2 5
# 线程级统计
pidstat -d -t 2
不建议用 cron 长期执行 iotop 并把交互输出写入日志:输出体积大、字段格式不适合解析,也容易忽略多核和多进程上下文。长期监控应优先使用 pidstat、node_exporter、Prometheus 或运行时自身指标。
blktrace:下探块层延迟
当 iostat 只能说明设备慢,但不知道慢在排队还是设备完成阶段时,使用 blktrace:
bash
# 限定设备并采集 30 秒
sudo blktrace -d /dev/sda -w 30 -o edge-trace
# 合并各 CPU 的原始 trace
sudo blkparse -i edge-trace -d edge-trace.bin -q
# 分析块层阶段耗时
sudo btt -i edge-trace.bin
常见阶段:
| 阶段 | 含义 | 异常时看什么 |
|---|---|---|
Q2I |
请求进入块层到插入队列 | 请求合并、分配、调度器行为 |
I2D |
插入队列到下发驱动 | 队列堆积、调度策略、cgroup 限制 |
D2C |
下发设备到设备完成 | 设备响应、固件、底层虚拟化或远程存储 |
Q2C |
块层总耗时 | 与 await 和应用延迟互相印证 |
blktrace 自身有开销,产线设备上要限定时间窗口和设备,采集前后同步保存 iostat、pidstat 和应用指标。
八、几个工程实践
实践 1:统一时间窗口
同一问题要保存同一时间段的 iostat、iotop、pidstat、应用日志和业务指标。只有时间戳对齐,才能区分"谁先触发"和"谁被拖慢"。
实践 2:先建立设备基线
机械盘、SATA SSD、NVMe、eMMC、SD 卡和 NFS 对同一延迟值的承受能力完全不同。没有正常班次和工况基线,就无法判断 20ms 或 50ms 是异常还是设备特性。
实践 3:区分速率、累计值和等待比例
速率回答当前压力,累计值回答任务贡献,IO / iodelay 回答任务等待。三者混用会导致误判。
实践 4:关注小块随机写
eMMC / SD 卡的寿命和写放大与擦除块相关。长期 4KB 以下碎片写、频繁 fsync、数据库小事务和日志刷盘,可能比短时间大文件顺序写更危险。
实践 5:把结论落成监控项
至少监控设备读写速率、IOPS、延迟、队列、进程读写、脏页、磁盘水位、设备错误、容器 I/O 和日志增长率。告警不要只设绝对值,应结合基线偏离、持续时间和业务影响。
九、常见的坑
坑 1:把 iotop 当唯一工具
iotop 只做进程 / 线程归因,不知道文件路径、业务语义和完整设备路径。必须与 iostat、pidstat、cgroup、文件系统和应用指标配合。
坑 2:误解 iotop -a
-a 是累计 I/O,不是实时速率,也不是采样周期内的增量。突发判断应使用默认速率模式。
坑 3:把 %util 当饱和唯一指标
现代 SSD / NVMe 可以并行处理请求。%util 只能作为辅助,应结合 await、aqu-sz、吞吐、IOPS 和应用延迟。
坑 4:以为 ionice 能限带宽
ionice 只设置调度类别和优先级,效果依赖 I/O scheduler;确定性限制应使用 cgroup v2 io.max 或存储 QoS。
坑 5:忽略 page cache 延后写入
应用 write() 返回快,不代表数据已经落盘。脏页回写可能在稍后出现,并被归属到内核回写线程或后台任务。
坑 6:容器内只看主 PID
主 PID 会遗漏子进程和线程。容器场景应结合进程树与 cgroup I/O 统计。
坑 7:长期用 cron 跑 iotop
iotop 输出偏交互且不利于结构化解析。长期采集优先使用 pidstat 或监控系统导出的指标。
十、TL;DR
iotop 负责回答"谁在做 I/O",iostat 回答设备层发生了什么,pidstat 更适合长期趋势,blktrace / btt 拆解排队与设备完成耗时,cgroup 用于容器归因和限制验证。工业边缘场景还要额外关注 eMMC 寿命、小块随机写、脏页回写、日志容量、远程存储映射和设备健康。不要把 iotop -a 当速率,不要把 %util 当唯一饱和指标,也不要把 ionice 当带宽限制。
如果要把分散的 I/O 证据沉淀为统一的边缘运行时观测能力,可以把 Zenova EdgeOS 的观测与告警设计作为协议运行时选型参考;具体阈值仍需结合实际设备、工况和数据链路验证。