1. 现象:业务卡顿,日志刷屏
某个数据库集群在晚间高峰期出现周期性延迟飙升,应用侧平均响应时间从 30ms 涨到 2s 以上。 第一直觉是 CPU 不够,但 top 一看负载并不高。真正的嫌疑指向磁盘。
排查 IO 问题的第一步,永远是用数据确认「确实是磁盘慢」,而不是凭感觉换硬件。
2. 第一步:iostat 看全局
用 iostat -x 1 每秒采样,重点关注以下指标:
$ iostat -x 1 5
Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %rrqm %wrqm
vda 12.00 210.00 1024.00 56320.00 3.00 180.00 20.0 46.2
r_await w_await aqu-sz rareq-sz wareq-sz svctm %util
8.20 96.40 8.75 85.33 268.19 10.55 100.0
关键解读:
- w_await 96ms —— 写请求平均延迟接近 100ms,明显异常。健康云盘通常应 < 20ms。
- aqu-sz 8.75 —— 队列深度持续堆积,请求在排队,不是盘慢就是压力过大。
- %util 100.0 —— 设备利用率打满。注意:对机械盘是实际利用率,对 SSD 需结合队列深度看。
- w_await 远大于 r_await —— 问题集中在写路径,大概率是随机写或刷盘策略导致。
注意
%util 100% 并不直接等于性能瓶颈。例如一块 NVMe SSD 单队列也能到 100%,但仍有大量余量。
判断依据是「延迟是否随之恶化」,而非单纯看利用率。
3. 第二步:pidstat 定位进程
确认磁盘慢之后,需要知道「是谁在写」。用 pidstat -d 1 看每个进程的 IO:
$ pidstat -d 1
12:30:01 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
12:30:02 0 1832 0.00 51200.00 0.00 380 mysqld
12:30:02 0 2441 0.00 800.00 0.00 5 journald
结果一目了然:mysqld 每秒写入 50MB 且 iodelay 高达 380ms。
到这里基本锁定是数据库写入引发,接下来确认是业务查询还是后台刷脏页。
$ sudo iotop -bk -d 1
Total DISK READ: 0.00 B/s | Total DISK WRITE: 51.20 MB/s
TID PRIO USER DISK READ DISK WRITE IO COMMAND
1832 be/4 mysql 0.00 B 51.00 M 100% mysqld
技巧
iostat 回答「盘慢不慢」,pidstat -d 回答「谁在慢」。
两者配合,一分钟内就能完成第一轮定位。
4. 第三步:用 fio 验证盘的真实能力
怀疑是存储层问题(例如云盘 IOPS 被限流、磁盘老化)时,用 fio 做基准测试。 用与业务相近的 IO 模式(本例为随机写、4K)进行压测:
$ fio --name=randwrite \
--ioengine=libaio --direct=1 \
--rw=randwrite --bs=4k --iodepth=32 \
--size=1G --numjobs=1 --runtime=30 \
--group_reporting
write: IOPS=3820, BW=14.9MiB/s (15.6MB/s)
lat (usec): min=115, max=15871, avg=8205.46, stdev=4321.2
clat percentiles (usec):
99.00th=[14746], 99.90th=[15843], 99.99th=[15843]
测试结论:
- 4K 随机写 IOPS 仅 3800,平均延迟 8ms,P99 接近 15ms。
- 对比云厂商该规格盘的承诺值(通常 ≥ 10000 IOPS),存在明显差距。
- 结合监控发现 IOPS 突发时被限流,判断为云盘 IOPS 配额不足,而非代码问题。
顺带验证顺序写作为对照:
$ fio --name=seqwrite --ioengine=libaio --direct=1 \
--rw=write --bs=1M --iodepth=16 \
--size=4G --runtime=30 --group_reporting
write: IOPS=4200, BW=4.1GiB/s (4.4GB/s)
lat (avg): 162.44 usec
5. 常见根因与对策
磁盘 IO 问题的根因通常落在以下四类,对照处置即可:
| 根因 | 症状 | 对策 |
| IOPS 配额不足 | fio 实测低于承诺值 | 升级云盘类型或容量 |
| 日志写入风暴 | journald 写量飙升 | 调整日志级别/轮转 |
| 数据库刷脏页 | 定期写峰值 | 调大 buffer_pool、错峰 |
| 文件系统碎片/超配 | 长期使用后变慢 | 迁移、碎片整理 |
回到本文的案例:最终通过提升云盘 IOPS 配额 + 调大 MySQL innodb_buffer_pool_size
减少落盘频率,晚高峰延迟恢复到 30ms 基线。
方法论总结
从 iostat 看全局 → pidstat/iotop 定位进程 → fio 验证硬件能力 → 对照根因处置。
用这套流程,大多数磁盘性能问题都能在一个小时内找到答案。
