结论: 磁盘 IO 过高不是"空间不够",而是"读写太忙"——df -h 看不出问题,要看 iostat -x 1。判断标准是三个数:%util 持续超过 80%、await 显著高于设备基线(HDD 超过 20 ms、SATA SSD 超过 10 ms、NVMe 超过 5 ms)、aqu-sz 持续大于 4。先用 iotop -o 和 pidstat -d 1 找到具体进程,再用 fio 测出设备真实能力,最后针对成因处理:临时用 ionice 限流备份任务止血,长期调 innodb_io_capacity、日志改异步写、换 SSD 或修 RAID 缓存。
很多运维看到服务器卡,第一反应是 top 里 %iowait 高,然后去清磁盘——但清空间对 IO 压力毫无帮助。IO 压力的本质是"单位时间内读写请求超过了设备能承受的量",与磁盘还剩多少 GB 无关。本文所有命令基于 Ubuntu 24.04 LTS + sysstat 12.x(截至 2026 年主流版本),RHEL 系路径基本一致,仅包名同为 sysstat。
先分清:磁盘空间不足与磁盘 IO 高是两件事
结论:两张表对照着看,能立刻判断该走"清理空间"还是"降低 IO 压力"的路线。
| 维度 | 磁盘空间不足 | 磁盘 IO 过高 |
|---|---|---|
| 典型现象 | 写入报 No space left on device | 业务变慢、超时,写入仍成功 |
| 核心指标 | df -h 使用率接近 100%、df -i inode 耗尽 | iostat -x 1 的 %util、await、aqu-sz 高 |
| 查看命令 | df -h、df -i、du -sh | iostat -x 1、iotop -o、pidstat -d 1 |
| 主要成因 | 日志堆积、大文件未清理 | 刷盘频繁、随机写、备份任务、swap 交换 |
| 处理方向 | 删除/归档/扩容 | 限流、调参、换介质、改写入模式 |
两者也会互相诱发:剩余空间越少,文件系统分配新块越困难,碎片化加剧,反而推高 IO。所以当 %util 高时,顺手确认一下使用率是否超过 80%,超过就先清理,能给文件系统留出更多连续空间。
# 一条命令同时看空间、inode 与 IO
df -h; df -i; iostat -x 1 3
# 没有 iostat 就先装 sysstat(Ubuntu/Debian)
sudo apt install -y sysstat iotop fio
sudo sed -i 's/^ENABLED="false"/ENABLED="true"/' /etc/default/sysstat
sudo systemctl enable --now sysstatiostat -x 1 输出解读:每个字段的正常值与告警阈值
结论:看五个字段就够了——r/s+w/s(IOPS)、await(延迟)、aqu-sz(队列)、%util(饱和度)、rareq-sz/wareq-sz(请求大小,用来判随机还是顺序)。
| 字段 | 含义 | HDD(机械盘) | SATA SSD | NVMe SSD | 告警阈值 |
|---|---|---|---|---|---|
r/s + w/s | 每秒读写请求数(IOPS) | 80~200 | 5k~50k | 10 万~100 万 | 接近设备标称上限的 80% |
await | 平均每次 IO 的等待+服务时间(ms) | 5~15 ms | 0.5~3 ms | 0.1~1 ms | HDD > 20 ms;SSD > 10 ms;NVMe > 5 ms |
aqu-sz | 平均请求队列长度 | < 1 | < 1 | < 1 | 持续 > 4 说明排队严重 |
%util | 设备处理 IO 的时间占比 | < 60% | < 60% | < 60% | 持续 > 80% |
rareq-sz / wareq-sz | 平均请求大小(KB) | 顺序 64~1024;随机 4~16 | 同左 | 同左 | 长期 4~8 KB 说明大量随机小 IO |
svctm | 平均服务时间 | 不可信,man 手册明确警告 | — | — | 不要用该字段判断 |
rkB/s + wkB/s | 每秒读写吞吐量 | 100~200 MB/s | 400~550 MB/s | 1~7 GB/s | 接近标称带宽上限 |
两点重要提醒:
- 新版 sysstat(12.x)把
await拆成了r_await与w_await,需要分别看读延迟和写延迟;写延迟单独飙高通常是刷盘或日志同步写导致。 %util对多队列设备(NVMe、RAID、云盘)意义有限:它只统计"有请求在处理"的时间占比,16 队列的 NVMe 即使%util显示 100%,也可能远未饱和。此时应以await与aqu-sz为准。
# 每 1 秒刷新,只看扩展统计(去掉 CPU 段让输出更干净)
iostat -x -d 1
# 只看某块盘,输出 10 次
iostat -x -d /dev/sda 1 10
# 按 MB 显示,更直观
iostat -x -d -m 1
# 配合 vmstat 看是不是 swap 在捣乱(si/so 非 0 即发生交换)
vmstat 1定位进程:iotop 与 pidstat 找出"谁在猛写"
结论:先用 iotop -o 一眼抓出正在做 IO 的进程,再用 pidstat -d 1 拿到每秒读写字节数做定量判断。
# 只显示真正在做 IO 的进程(最常用)
sudo iotop -o -P -d 1
# 累计 IO 量排序,适合抓"间歇性大量写"的进程
sudo iotop -a -o -P
# 每 1 秒输出各进程的读写速率(kB_rd/s、kB_wr/s)
pidstat -d 1
# 指定进程的精确 IO 统计(内核级计数,比 iotop 更准)
cat /proc//io
# 关注 read_bytes / write_bytes(实际落盘量)与 rchar / wchar(系统调用量)
# 查看该进程打开了哪些文件(删除未释放的大文件常在这里现形)
sudo lsof -p | grep -i "deleted\|log\|data" 判断技巧:
iotop里DISK WRITE一列持续几百 MB/s,且进程名是mysqld,多半是脏页刷盘或大查询写临时表。- 进程名是
rsync、tar、xtrabackup、gzip,基本就是备份任务在跑,直接限流或改时段。 - 出现
jbd2/sda1-8、kworker等内核线程占用高,说明文件系统日志(journal)在频繁提交,通常是上层有大量fsync调用。 %iowait高但没有任何用户态进程 IO 高,检查vmstat的si/so,可能是 swap 交换。
随机写还是顺序写:决定优化方向
结论:看 rareq-sz/wareq-sz 与 rkB/s ÷ r/s 的比值——大请求是顺序、小请求是随机,两者的优化手段完全不同。
| 特征 | 顺序读写 | 随机读写 |
|---|---|---|
| 平均请求大小 | > 64 KB,常达 128 KB~1 MB | 4~16 KB |
| 典型场景 | 大文件拷贝、视频服务、备份、日志追加 | 数据库 OLTP、小文件读写、碎片化缓存 |
| 瓶颈指标 | 吞吐量(MB/s) | IOPS 与延迟 |
| 优化方向 | 提升带宽、合并写入、预读 | 换 SSD/NVMe、加大缓存、合并随机为顺序 |
# 计算平均请求大小:吞吐 ÷ IOPS
# 若 wkB/s = 4096 且 w/s = 1024,则平均 4 KB/次 → 典型随机写
iostat -x -d 1 | awk '/^sd|^nvme/ {printf "%s w/s=%s wkB/s=%s avg=%dKB\n", $1, $2, $4, ($2>0? $4/$2 : 0)}'
# 用 /sys 直接看设备的请求统计
cat /sys/block/sda/stat
# 第 4 列为读次数、第 8 列为写次数随机写为主的数据库场景,优化重点是把随机写变成顺序写(如 MySQL 的 redo log 就是顺序写、change buffer 延迟写二级索引)以及加大内存缓存减少落盘次数;顺序写为主的场景则要提高单次写入大小、减少小 IO 次数。
用 fio 量化磁盘真实能力
结论:不知道设备上限就无法判断"高不高",用 fio 测出 IOPS 与带宽基线,才知道该优化到什么程度。
# 随机写 IOPS(数据库最关心的指标)
fio -name=randwrite -ioengine=libaio -direct=1 -rw=randwrite -bs=4k \
-numjobs=1 -iodepth=32 -size=1G -runtime=60 -time_based -group_reporting \
-filename=/data/fio.test
# 随机读 IOPS
fio -name=randread -ioengine=libaio -direct=1 -rw=randread -bs=4k \
-numjobs=1 -iodepth=32 -size=1G -runtime=60 -time_based -group_reporting \
-filename=/data/fio.test
# 顺序写带宽(备份、日志类场景)
fio -name=seqwrite -ioengine=libaio -direct=1 -rw=write -bs=1M \
-numjobs=1 -iodepth=8 -size=2G -runtime=60 -time_based -group_reporting \
-filename=/data/fio.test
# 模拟数据库混合随机读写(读 70% / 写 30%)
fio -name=db-mix -ioengine=libaio -direct=1 -rw=randrw -rwmixread=70 -bs=16k \
-numjobs=4 -iodepth=32 -size=2G -runtime=120 -time_based -group_reporting \
-filename=/data/fio.test跑 fio 的三条铁律:
- 务必加
-direct=1,绕过页缓存,否则测的是内存速度,结果虚高几十倍。 - 别在业务系统盘上跑满负载,测试文件放到独立目录或空闲数据盘;生产环境建议把
-runtime控制在 60 秒以内并避开高峰。 - 测完删除测试文件:
rm -f /data/fio.test,否则会占空间并影响后续测试准确性。
结果重点看三行:IOPS=(随机场景核心)、BW=(吞吐,顺序场景核心)、clat 的 99.00th 百分位延迟(长尾延迟比平均值更能反映用户体验)。
常见高 IO 成因与处理对照表
结论:七类成因覆盖了绝大多数线上场景,按特征列对号入座。
| 成因 | 识别特征 | 验证命令 | 处理方式 | |
|---|---|---|---|---|
| MySQL 刷盘(脏页/checkpoint) | mysqld 进程写高、redo log 太小 | SHOW ENGINE INNODB STATUS | 调大 redo、改 innodb_io_capacity | |
| 日志同步写 | jbd2、rsyslog、Nginx 等写高 | iotop -o 看进程名 | 日志加 buffer、改异步、降级别 | |
| 大量小文件 | wareq-sz 只有 4~8 KB、inode 消耗快 | df -i、目录文件数统计 | session 迁 Redis、缓存合并 | |
| RAID 卡无缓存或 BBU 故障 | 写延迟突然暴涨数倍 | storcli/megacli 看 Cache Policy | 更换 BBU/电容,恢复 WriteBack | |
| swap 交换 | vmstat 的 si/so 非 0 | vmstat 1、free -h | 调低 swappiness、加内存 | |
| 备份任务占用 | rsync/tar/xtrabackup 写高 | `ps aux \ | grep rsync` | ionice 限流、错峰执行 |
| 云盘 IOPS 打满 | IOPS 卡在固定值不再上涨 | 对比控制台监控与 iostat | 升配云盘、换 ESSD 高 IOPS 档 |
BBU(电池备份单元)故障是最容易被忽略的一类:RAID 卡检测到 BBU 或超级电容失效后,会自动把写缓存策略从 WriteBack 降级为 WriteThrough,写性能可能暴跌 5~10 倍。
# LSI/Avago 系列查看缓存策略(WriteBack 正常,WriteThrough 说明降级)
sudo storcli /c0 /v0 show | grep -i "cache"
# 老版本 MegaCLI
sudo MegaCli64 -LDInfo -Lall -aAll | grep -i "Cache Policy"
sudo MegaCli64 -AdpBbuCmd -aAll | grep -i "Battery State"临时止血与长期优化
结论:先止血恢复业务,再动参数——顺序反了很容易在高压状态下改配置引发二次故障。
临时止血三招:
# 1) 用 ionice 给备份/批处理进程降级(-c2 -n7 为最低优先级,-c3 为仅在空闲时执行)
sudo ionice -c2 -n7 -p
sudo ionice -c3 -p
# 启动时直接指定
ionice -c2 -n7 rsync -av /data/ user@backup:/data/
# 2) 暂停非关键定时任务(备份、日志归档、报表生成)
sudo systemctl stop backup.timer
crontab -l | grep -n "backup\|dump"
# 3) cgroup v2 直接限制进程的写带宽(如限到 20 MB/s)
sudo mkdir -p /sys/fs/cgroup/limit-io
echo > /sys/fs/cgroup/limit-io/cgroup.procs
echo "8:0 wbps=20971520" > /sys/fs/cgroup/limit-io/io.max 长期优化——MySQL 侧参数(/etc/mysql/mysql.conf.d/mysqld.cnf):
[mysqld]
# 按介质设定 IO 能力:HDD 200~1000,SATA SSD 1000~4000,NVMe 5000~20000
innodb_io_capacity = 2000
innodb_io_capacity_max = 4000
# SSD/NVMe 关闭邻居页合并(HDD 保持 1 可减少随机寻道)
innodb_flush_neighbors = 0
# redo log 容量(MySQL 8.0.30+ 用此参数替代 innodb_log_file_size)
# 太小会导致频繁 checkpoint 与刷盘风暴
innodb_redo_log_capacity = 2G
# 提交刷盘策略:1 最安全最慢;2 每秒刷一次,宕机最多丢 1 秒
innodb_flush_log_at_trx_commit = 2
sync_binlog = 100
# 缓冲池越大,脏页落盘越少(专用机建议物理内存的 60%~70%)
innodb_buffer_pool_size = 8G日志侧的长期优化:
# Nginx:为 access_log 加缓冲,减少每次请求的同步写
http {
access_log /var/log/nginx/access.log main buffer=64k flush=5s;
# 静态资源访问量大时可单独关闭或降低记录级别
access_log off; # 仅在确定不需要访问日志时使用
}其余长期手段还包括:把 PHP/Java 的 session 与碎片化缓存迁到 Redis;用 tmpfs 承载 /tmp 与可重建的缓存目录;将备份窗口挪到业务低峰并配合 ionice -c3;机械盘场景优先升级到 SSD 或 NVMe(截至 2026 年,主流云厂商 ESSD 入门档已能提供数千 IOPS,成本相对机械盘已无显著劣势)。
企业QQ咨询




