磁盘 I/O 100% 排查实战:从 iotop 到定位根因

服务器突然变慢、接口超时、连 SSH 敲命令都卡?打开监控一看,磁盘 I/O 利用率 100%。磁盘 I/O 打满和 CPU 100%、内存 swap 一样,是线上最常见的性能”暴雷”之一。本文用一套可复用的排查流程,带你从 iotop、iostat 一路定位到具体进程和文件,把磁盘 I/O 瓶颈彻底摸清。

一、先分清:是磁盘满了,还是 I/O 打满了

很多人看到”磁盘”二字就先 df -h,但空间满和I/O 打满是两回事:空间满会报 “No space left on device”,文件系统可能直接变只读(参见磁盘只读故障排查实战);而 I/O 打满时空间还很多,只是读写请求排队、延迟飙升。第一步一定要先排除空间问题。

# 1) 看空间是否满了(Use% 接近 100% 才是空间问题)
df -h

# 2) 看 inode 是否耗尽(小文件极多时空间没满但 inode 满了)
df -i

如果 df 显示还有大量空闲空间,那基本可以确定是磁盘 I/O 瓶颈,进入下一步。

I/O 饱和的典型体感很”反直觉”:用 top 看 CPU 使用率可能并不高,但系统负载(load average)却飙得离谱。原因是进程在等磁盘时进入不可中断睡眠(D 状态),不消耗 CPU 却卡在调度队列里。所以当你看到”机器很闲但什么都慢”,第一反应就应该是查磁盘 I/O,而不是 CPU(若确为 CPU 问题可参考CPU 占用 100% 排查)。

二、第一步:用 iotop 揪出”读写大户”进程

iotop 类似 top,但按磁盘读写速率排序,能直接看到哪个进程在疯狂刷盘。需要 root 权限,且内核要开启 TASK_DELAY_ACCT / CONFIG_TASK_IO_ACCOUNTING(主流发行版默认开启)。

# 只显示真正在读写 IO 的进程(最常用)
sudo iotop -o

# 按列排序:-d 3 表示每 3 秒刷新一次
sudo iotop -o -d 3

# 批处理模式,抓取 5 次输出到文件,便于事后分析
sudo iotop -o -b -n 5 > /tmp/iotop.log

iotop 关键列怎么读

  • DISK READ / DISK WRITE:该进程当前的读/写速率,单位通常 B/s、K/s、M/s。
  • SWAPIN:换入比例,偏高说明在和内存 swap 互相拖累。
  • IO>%:进程等待 I/O 的时间占比,长期接近 100% 就是它把盘吃满了。

三、第二步:用 iostat 看清”盘到底有多忙”

iotop 告诉我们”谁在写”,iostat 告诉我们”盘有多忙、延迟多大”。iostat 来自 sysstat 包,重点关注 -x(扩展)参数。

光看 %util 还不够,还要结合 aqu-sz(平均请求队列长度)和 rkB/s / wkB/s(读写吞吐量)一起看:队列越长、吞吐越接近磁盘规格上限,瓶颈越实锤。特别提醒云服务器用户——阿里云/腾讯云的云盘都有套餐 IOPS 与带宽上限(如 ESSD PL1 约 5 万 IOPS),业务突发超过配额就会被限速,表现为 await 暴涨。怀疑云盘被限速时,可到控制台核对”云盘监控”确认是否触顶。

# -x 扩展统计,-d 只看磁盘,1 每秒刷新,3 共 3 次
iostat -x -d 1 3

await、svctm、%util 三件套

指标含义警戒线
%util设备繁忙时间占比持续 ≥95% 即 I/O 饱和
await单次 I/O 平均等待+服务总时长机械盘 >20ms、SSD >5ms 需警惕
svctm设备实际服务时长应远小于 await,差距大说明在排队
r/s、w/s每秒读/写次数(IOPS)结合盘规格判断是否到上限

经验法则:%util 长期 100% 且 await 远大于 svctm,说明请求在队列里排长队——这就是典型的 I/O 瓶颈信号。

四、第三步:用 lsof / /proc 定位具体文件

找到读写大户进程(假设 PID 为 1234)后,进一步定位它在读写哪个文件。

# 该进程当前打开的所有文件(含路径、读写模式)
lsof -p 1234

# 只看该进程正在写入(REG 普通文件、FD 带 w)的文件
lsof -p 1234 | grep -E 'REG.*w'

# 查看进程当前的文件描述符与对应文件(见 <a href="https://fsdata.site/2026/10/too-many-open-files-troubleshooting/">文件描述符</a> 相关排查)
ls -l /proc/1234/fd

# 实时看该进程的系统调用,找 read/write 落点
sudo strace -e trace=read,write -p 1234 -f 2>&1 | head -50

五、常见根因清单

根因典型表现方向
日志疯狂刷盘某服务 DEBUG 日志无级别、无限速日志分级 + 异步写 + 限流
全表扫描 / 慢 SQL数据库磁盘读 100%,伴随慢查询加索引、优化 SQL、读写分离
频繁 fsync / 小文件写IOPS 高、吞吐低,await 大批量写、合并提交、上 SSD
备份 / 同步任务占峰定时点 I/O 突增错峰、限速(ionice/nice)
内存不足触发 swapSWAPIN 高、读写混杂加内存、调 swappiness

六、真实案例:日志无脑刷盘拖垮整台机器

某次线上告警,一台 4C8G 的机器磁盘 %util 长期 100%、接口 P99 从 80ms 涨到 3s。用 iotop -o 一眼看到是 app-server 进程在持续以 120MB/s 写盘。

复现与定位

# 确认是 app-server(PID 1234)在写
sudo iotop -o -b -n 3

# 定位它在写哪个文件
lsof -p 1234 | grep REG

# 输出指向 /var/log/app/debug.log,且体积每分钟涨上百 MB
ls -lh /var/log/app/debug.log

修复:分级 + 异步 + 限速三连

根因是生产环境开了 DEBUG 日志且同步落盘。修复分三步:① 日志级别调到 INFO;② 改为异步 appender,不阻塞业务线程;③ 用 logrotate 配合 size 触发轮转,并用 ionice 把日志进程降到空闲调度。

修复上线后不要凭感觉收工,务必回到 iostat -x 1 观察 1~2 分钟:%util 应从 100% 回落到正常区间,await 从几百毫秒降到个位数毫秒,wkB/s 也不再异常尖峰。只有指标真正回归,才算排查闭环。后续再把”日志级别””异步写””轮转策略”写进该服务的部署规范,避免同类问题复发。

# 给日志进程(PID 1234)设置 IO 调度优先级为 idle,限制其对盘的挤占
sudo ionice -c 3 -p 1234

# 或启动时直接指定
sudo ionice -c 3 app-server > /var/log/app/app.log 2>&1 &

七、第四步(可选):下钻到块层

如果应用层找不到明显大户,说明瓶颈在更底层(文件系统、RAID、云盘节流)。可用 blktrace + blkparse,或用 biosnoop(bpftrace/eBPF 工具)追踪每一次块 I/O 的源头,看到底是哪类请求在占盘。

八、修复后验证与长期监控

修复后务必回到 iostat -x 1 看 %util 是否回落、await 是否下降;同时把磁盘 I/O 指标接进监控,避免下次”暴雷”无人知。如果你更关心 CPU 层面的排查,可参考CPU 占用 100% 线上排查实战。

用 Prometheus + node_exporter 监控磁盘 I/O

groups:
- name: disk-io
  rules:
  - alert: DiskIOSaturation
    expr: avg by (device) (rate(node_disk_io_time_seconds_total[5m])) * 100 > 90
    for: 10m
    labels:
      severity: warning
    annotations:
      summary: "磁盘 {{ $labels.device }} I/O 利用率持续 > 90%"

九、可复用的磁盘 I/O 排查清单

步骤命令看什么
1 排除空间df -h / df -iUse%、Inode 是否满
2 找进程sudo iotop -o哪个进程 IO>% 高
3 看盘忙iostat -x -d 1 3%util、await vs svctm
4 定位文件lsof -p PID读写的是哪个路径
5 下钻strace / blktrace系统调用 / 块层来源
6 验证iostat -x 1%util、await 是否回落

十、总结

磁盘 I/O 100% 不可怕,可怕的是没有章法地乱试。记住这条主线:df 排除空间 → iotop 找进程 → iostat 看盘忙 → lsof 定位文件 → 分级/异步/限速/错峰修复 → 监控守住。把这套流程存进你的排查 SOP,下次再遇磁盘 I/O 打满,十分钟就能定位根因。

上一篇 磁盘 I/O 100% 排查实战:从 iotop 到定位根因
下一篇 uv 包管理器实战:Python 依赖与虚拟环境提速
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]