用 fio 做磁盘性能基准测试:读懂 IOPS、吞吐与延迟
新盘上线、云盘选型、存储变慢投诉,最后都要落到一个问题上:这块盘到底有多快。随手用 dd 或复制文件测出来的数字往往不可复现、不可对比,换个参数就变样。fio 是磁盘基准测试的事实标准,它能精确控制读写比例、块大小、并发与队列深度,并给出延迟分布。本文讲清测试前必须统一的口径、四种经典负载模型的写法、结果该怎么读,以及最容易测出假数据的几个坑。适用环境为任意 Linux 发行版,被测对象可以是本地盘、云盘或挂载的块设备。
前置条件
- 已安装 fio(发行版仓库一般都有,版本 3.x 即可);
- 确认测试目标路径以及它是裸设备还是文件系统上的文件,两者结论完全不同,不要混谈;
- 测试盘上没有正在运行的生产业务:fio 写测试会覆盖目标区域数据,裸盘测试前务必再三确认设备名;
- 预留足够的测试时长:每项至少 60 秒并配合预热(ramp_time),短跑测的是缓存和突发性能,不是稳态。
一、测试前的关键认知:口径先于数字
脱离口径谈 IOPS 毫无意义,同一块盘在 4K 随机读和 1M 顺序读下 IOPS 能差两个数量级。每次测试至少要交代六件事:测的是裸盘(filename=/dev/vdb)还是文件系统里的文件;在文件系统上测时,文件大小必须显著大于内存,否则测的是页缓存;是否绕过缓存(direct=1 是最关键的开关,不加它,读测的很可能是内存速度);块大小(bs)是多少;并发口径是 iodepth(单任务队列深度)还是 numjobs(任务数),两者乘积才是总并发;最后是读写模型与比例(randread、randwrite、read、write、randrw 及 rwmixread)。把这六项固定下来,不同盘、不同时间测的数据才可比。
还要理解"稳态"的概念:SSD 和带写缓存的云盘在测试初期都会有一段虚高的突发期,写满缓存或触发 SLC 缓存回收后速度明显回落。只跑 10 秒得到的是突发值,业务高峰期跑的是稳态值,采购验收和容量规划应该认后者。对策是 ramp_time 先预热、runtime 拉长,并多次重复取一致的结果。
二、四种经典模型与 job 文件写法
零散的命令行参数容易记混、难复用,推荐把测试定义写成 job 文件,一份文件里用多个 section 描述多个场景,全局参数放 [global]。下面这份覆盖四种经典模型:4K 随机读(看 IOPS 上限)、4K 随机写(看写 IOPS 与延迟)、1M 顺序读(看吞吐上限)、4K 混合随机读写 7:3(最接近数据库类业务的形态)。
[global]
ioengine=libaio
direct=1
group_reporting
filename=/data/testfile
size=32G
ramp_time=10
runtime=60
time_based
[randread-4k]
rw=randread
bs=4k
iodepth=32
numjobs=1
[randwrite-4k]
rw=randwrite
bs=4k
iodepth=32
numjobs=1
[read-1m]
rw=read
bs=1M
iodepth=8
numjobs=1
[randrw-73]
rw=randrw
rwmixread=70
bs=4k
iodepth=32
numjobs=1
单条命令行的快速写法适合临时验证,与 job 文件等价:
# 4K 随机读快速测试:libaio 引擎、直读绕缓存、队列深度 32、跑 60 秒
fio --name=randread --filename=/data/testfile --size=32G --rw=randread \
--bs=4k --iodepth=32 --ioengine=libaio --direct=1 --runtime=60 --time_based --group_reporting
# 测裸设备时把 filename 换成设备路径(务必确认设备上无数据!)
fio --name=rawtest --filename=/dev/vdb --rw=randread --bs=4k --iodepth=32 \
--ioengine=libaio --direct=1 --runtime=60 --time_based --group_reporting
参数里最容易被忽略的是 ioengine:Linux 下做高并发测试用 libaio 或 io_uring,默认的 sync 引擎一次只发一个请求,测出来的永远是低并发下的数据。另外 numjobs 与 iodepth 不要同时盲目拉大:总请求在途数 = numjobs × iodepth,先固定一个、扫另一个,才能看出盘的队列深度拐点在哪里。
三、结果解读:IOPS、吞吐与延迟各看一行
fio 输出里每个 job 会给出 IOPS、BW(吞吐)和 clat(完成延迟)三组核心数据。读的时候注意对应关系:小块随机场景看 IOPS,大块顺序场景看 BW,两者可以互相换算(吞吐 ≈ IOPS × 块大小),对不上说明口径理解有误。延迟要看 clat 的百分位而不是平均值——fio 会输出从 50% 到 99.99% 的分布。平均延迟会被少量长尾拉偏或掩盖:avg 只有 0.5ms 的盘,99.9% 分位可能已经到 20ms,而在线业务的超时恰恰由长尾决定。评估一块盘能不能扛业务,顺序是先看目标块大小下的 IOPS/BW 是否达标,再看 99% 与 99.9% 分位延迟是否在业务可接受范围内。
randread-4k: (groupid=0, jobs=1): err= 0: pid=1234: Sun Nov 8 21:00:00 2026
read: IOPS=52.3k, BW=204MiB/s (214MB/s)(11.9GiB/60001msec)
slat (usec): min=2, max=890, avg= 5.12, stdev= 6.33
clat (usec): min=21, max=18234, avg=605.44, stdev=388.21
lat (usec): min=24, max=18241, avg=610.87, stdev=390.02
clat percentiles | usec:
| 1.00th=[ 334], 5.00th=[ 402], 10.00th=[ 451], 20.00th=[ 498]
| 30.00th=[ 529], 40.00th=[ 560], 50.00th=[ 586], 60.00th=[ 611]
| 70.00th=[ 643], 80.00th=[ 685], 90.00th=[ 750], 95.00th=[ 832]
| 99.00th=[ 1159], 99.50th=[ 1467], 99.90th=[ 3326], 99.95th=[ 5014]
| 99.99th=[12256]
上面这份输出的读法是:4K 随机读 52.3k IOPS、吞吐 204MiB/s,与 IOPS×4K 的换算吻合;平均完成延迟 0.6ms 看起来不错,但 99.9% 分位已经 3.3ms、99.99% 到 12ms,若是给延迟敏感的数据库用,这块盘的长尾就需要进一步关注,而不是只报"IOPS 五万二"了事。还需要机器可读结果时,加 --output-format=json 输出 JSON,便于脚本采集入库做长期对比;加 --eta=always 可以在长测试中看到进度。
四、云盘与本地 NVMe 的对比测法
云盘与本地盘的差异主要在延迟基线与稳定性:本地 NVMe 的 4K 读延迟通常在百微秒级,云盘由于多了一层网络与分布式存储,延迟在亚毫秒到数毫秒,且受邻居流量影响会有波动。对比测法的要点是"同口径、多时段":同一份 job 文件,在同一规格云盘与本地 NVMe 上各跑三遍以上,并把云盘的测试分散到不同时段执行,才能看出云盘的延迟抖动幅度。云盘还要额外关注厂商承诺的口径——很多云盘的 IOPS 上限与盘容量挂钩,小容量盘测不出高 IOPS 不是盘的问题,是规格限制,先查规格表再下结论。
五、测试的坑:三个最常见的假数据来源
第一是文件太小测了缓存:测试文件必须大于物理内存且 direct=1,即使开了 direct,也要留意控制器与盘自身的缓存,写测试初期虚高就是它。第二是口径不一致:拿 iodepth=1 的延迟去比 iodepth=32 的 IOPS,或者拿顺序吞吐去对随机 IOPS,数字都对、结论全错。第三是测出了远超标称的"假象":常见于测试文件落在带压缩或重复数据删除的存储上,fio 默认写的是可压缩数据模式,有些盘会因此跑出离谱高分;用 buffer_compress_percentage 控制或写入随机数据可以规避。另外在虚拟化环境里还要确认盘没有被限速策略(cgroup blkio、云盘规格上限)卡住,测到的是策略值而不是盘的真实能力。
验证
- 同口径重复测试三次,IOPS 与吞吐的波动在 5% 以内,说明结果稳定可复现;
- 换算自洽:吞吐 ≈ IOPS × 块大小,例如 4K 块下 50k IOPS 应对应约 200MiB/s,对不上就要回头查口径;
- 对照盘的规格标称值量级合理,既没有低得离谱(可能被限速或参数写错),也没有高得离谱(多半测到了缓存)。
常见问题
- 测出的 IOPS 只有标称的零头。依次排查:ioengine 是否为 libaio/io_uring、iodepth 是否太小、测试文件是否用了 direct=1 反被文件系统碎片拖累、云盘是否受容量对应的 IOPS 上限限制。
- 顺序读吞吐远低于随机读换算值。顺序读要调大块大小(1M)并适当提高 iodepth;单线程 sync 引擎做顺序读也很容易跑不满。先统一参数再比较。
- 每次跑的结果差异巨大。多为没过稳态:加大 ramp_time 与 runtime、测试文件加大、避开其他业务干扰时段;云盘还需考虑邻居影响,分时段多跑取中位数。
- fio 把测试盘写坏了数据。写类测试对裸设备是破坏性的。养成习惯:裸盘测试前用 lsblk 与挂载信息双重确认设备,用文件测试时指定独立测试目录,测完删除测试文件回收空间。
小结
fio 测盘的要领是先统一口径再谈数字:裸盘还是文件、文件大小与 direct 决定有没有测到缓存,块大小、iodepth 与 numjobs 决定并发形态,读写模型决定场景。结果解读抓住三组数:小块看 IOPS、大块看吞吐、延迟必看 99% 与 99.9% 分位。守住这些原则,再避开文件过小、口径混用、可压缩数据这三个坑,测出来的数据才经得起复测和横向对比。






