基准测试方法论:FIO工具深度使用、测试场景设计与结果解读
存储性能测试,说白了就是给存储系统“体检”。你想想看,没有基准数据,你怎么知道系统瓶颈在哪?怎么判断优化有没有效果?我见过太多团队,一上来就扔个FIO跑个默认参数,然后拿着结果拍脑袋做决策——这跟闭着眼睛开车没区别。
这一章,我会把FIO的用法、场景设计、结果解读这些核心内容掰开揉碎讲清楚。嗯,都是我在一线摸爬滚打攒下来的经验。
2.1 FIO工具深度使用
FIO(Flexible I/O Tester)是目前业界最主流的存储基准测试工具。我个人习惯把它当作“瑞士军刀”——参数多,但用好了能解决绝大多数测试需求。
2.1.1 核心参数速查
先记住这几个最常用的参数,其他的可以边用边查:
| 参数 | 含义 | 典型值 |
|---|---|---|
--rw |
读写模式 | randread / randwrite / read / write / randrw / rw |
--bs |
块大小 | 4k / 64k / 1m |
--iodepth |
IO队列深度 | 1 / 16 / 64 / 128 |
--numjobs |
并发任务数 | 1 / 4 / 8 / 16 |
--size |
测试数据量 | 10G / 100G |
--runtime |
测试时长 | 60s / 300s |
--direct |
绕过文件系统缓存 | 1(必须开启) |
--help 确认版本。不同版本的FIO,某些参数行为有差异。我曾经因为版本问题,浪费了整整一个下午排查结果异常。
2.1.2 典型命令模板
直接上干货,这几个模板覆盖了90%的测试场景:
# 4K随机读(延迟敏感型)
fio --name=4k-randread --rw=randread --bs=4k --iodepth=1 --numjobs=1 \
--size=10G --runtime=60 --direct=1 --group_reporting
# 4K随机写(OLTP场景)
fio --name=4k-randwrite --rw=randwrite --bs=4k --iodepth=64 --numjobs=4 \
--size=100G --runtime=120 --direct=1 --group_reporting
# 64K顺序读(大文件流式读取)
fio --name=64k-seqread --rw=read --bs=64k --iodepth=16 --numjobs=1 \
--size=100G --runtime=60 --direct=1 --group_reporting
# 混合读写(7:3读写比)
fio --name=mix-70-30 --rw=randrw --rwmixread=70 --bs=4k --iodepth=32 \
--numjobs=4 --size=50G --runtime=120 --direct=1 --group_reporting
--size 不要超过设备可用容量的80%,否则FIO会报错,甚至导致测试结果失真。
2.2 测试场景设计
场景设计是基准测试的灵魂。没有场景,参数就是一堆数字。我一般把场景分为三类:随机、顺序、混合。下面这张图能帮你快速理解它们的关系:
2.2.1 4K随机:数据库场景模拟
4K随机读写是衡量存储系统IOPS(每秒IO次数)能力的核心场景。为什么是4K?因为数据库的页大小通常是8K或16K,但底层存储的原子操作单位是4K。说白了,4K随机就是存储系统的“百米冲刺”——考验的是控制器、闪存颗粒、驱动栈的综合响应能力。
我在项目中遇到过一个问题:某数据库集群的IOPS始终上不去,业务方怀疑是存储瓶颈。我跑了一组4K随机读测试,发现单盘IOPS只有8000,而标称值是15000。排查下来,是RAID卡缓存策略设置成了Write Through,改成Write Back后,IOPS直接翻倍。
2.2.2 64K顺序:大文件吞吐场景
64K顺序读写对应的是视频流、日志归档、大数据批量处理这类场景。这时候关注的是带宽(MB/s),而不是IOPS。你想想看,顺序读写时,存储系统可以预取、合并IO请求,效率远高于随机。
我建议顺序测试至少跑60秒以上,因为很多存储设备有SLC Cache机制——前几秒跑得飞快,后面就掉速了。我曾经测试一款NVMe SSD,前10秒顺序写能到3.5GB/s,30秒后稳定在1.8GB/s。如果不跑够时间,你拿到的就是“假数据”。
2.2.3 混合读写:真实负载模拟
混合读写是最接近真实业务的场景。我一般用7:3(读70%写30%)来模拟OLTP,用5:5来模拟日志型应用。混合测试的关键参数是 --rwmixread,它控制读写比例。
--randrepeat 参数,导致每次测试的IO分布完全一样,结果看起来“很稳定”,但实际上掩盖了存储系统的真实波动。建议加上 --randrepeat=0 让每次测试的随机种子不同。
2.3 结果解读与可信度评估
跑完测试只是第一步,读懂结果才是真本事。FIO的输出信息量很大,我教你抓重点。
2.3.1 核心输出字段解读
以下是一份典型的FIO输出:
Jobs: 4 (f=4): [r(4)][100.0%][r=120MiB/s,w=0KiB/s][r=30.7k,w=0 IOPS][eta 00m:00s]
read: IOPS=30.7k, BW=120MiB/s (126MB/s)(7200MiB/60001msec)
slat (usec): min=2, max=78, avg=4.35, stdev=3.21
clat (usec): min=15, max=4523, avg=128.67, stdev=89.45
lat (usec): min=18, max=4601, avg=133.02, stdev=92.66
cpu : usr=12.36%, sys=18.74%, ctx=89231, majf=0, minf=12
IO depths : 1=0.1%, 2=0.3%, 4=1.2%, 8=4.5%, 16=12.8%, 32=81.1%
| 字段 | 含义 | 关注点 |
|---|---|---|
| IOPS | 每秒IO次数 | 随机场景核心指标 |
| BW | 带宽(MiB/s或MB/s) | 顺序场景核心指标 |
| slat | 提交延迟(应用层→内核) | 通常很小,异常大说明驱动或系统调用有问题 |
| clat | 完成延迟(内核→设备→完成) | 存储系统真实延迟,重点关注 avg 和 p99 |
| lat | 总延迟(slat + clat) | 端到端延迟 |
| cpu | CPU使用率(usr/sys) | sys过高说明内核IO栈有瓶颈 |
| IO depths | IO队列深度分布 | 理想情况是集中在目标深度附近 |
2.3.2 可信度评估三原则
拿到结果后,别急着下结论。先问自己三个问题:
- 测试环境是否干净?——有没有其他进程在读写同一块盘?后台有没有定时任务?我遇到过因为crontab触发了日志压缩,导致测试结果波动30%的情况。
- 预热期是否足够?——很多存储设备有缓存预热机制。我建议正式测试前先跑10-20秒的“热身”数据,然后丢弃这部分结果。
- 多次测试是否一致?——单次测试结果不可信。我习惯每个场景跑3次,取中位数。如果三次结果偏差超过10%,说明测试环境有问题,需要排查。
--output-format=json 输出JSON格式结果,方便后续用脚本做批量分析和可视化。我个人写了个小工具,能自动对比多次测试的IOPS和延迟分布,一眼就能看出异常。
2.3.3 常见“假数据”陷阱
嗯,这里要重点说说那些看起来漂亮、实际上没用的测试结果:
- 缓存命中导致的虚高:没加
--direct=1,结果数据全在内存里,IOPS几十万,实际落盘性能一塌糊涂。 - 测试时间过短:只跑10秒,SLC Cache还没用完,结果好看,但上线后直接打回原形。
- iodepth设置不合理:数据库通常iodepth=1或2,你非要用iodepth=128去测,拿到的IOPS再高也没有参考价值。
- numjobs和iodepth混用不当:numjobs=16 + iodepth=128 = 2048并发,远超实际业务需求,结果只能用来“跑分”,不能指导生产。
基准测试不是跑分游戏。你拿到的每一个数字,都要能回答一个问题:这个数字对应的是业务场景下的什么表现?想清楚这个,你的测试才真正有价值。