1. 为什么我劝你先别急着敲fio命令
很多人第一次接触磁盘性能测试,都是因为线上服务突然变慢,怀疑是磁盘IO拖了后腿。于是搜到fio这个工具,复制一条命令跑下去,看到一堆数字就以为掌握了真相。我早期也这么干过,结果拿着错误的结果去跟存储团队对峙,最后发现是自己测试方法有问题,场面相当尴尬。
fio的全称是Flexible I/O Tester,直译过来就是“灵活IO测试器”。它的核心价值在于能模拟各种真实业务场景下的IO负载——数据库的随机读写、日志系统的顺序追加、视频转码的大块连续读写,这些都能通过参数组合精确复现。但问题也恰恰出在这里:参数太多,组合太灵活,不同参数组合出来的结果可能天差地别。你测出来磁盘能跑50万IOPS,换一组参数可能连5万都不到,两个结果都没错,但对应的是完全不同的业务场景。
这篇文章面向的是需要在Linux环境下做磁盘性能评估的运维、后端开发和系统工程师。我会从部署开始,把fio的安装、参数含义、测试模型设计、结果解读、常见误区和排查思路完整讲一遍。读完你至少能做到三件事:第一,根据业务特征设计出合理的测试方案;第二,看懂fio输出里哪些数字真正重要;第三,遇到结果异常时知道从哪几个方向排查。文章里涉及的操作步骤和参数配置都经过实际验证,你可以直接参考使用。
2. fio的部署:包管理器与源码编译怎么选
2.1 各发行版安装方式对比
fio在主流Linux发行版的官方仓库里都有,直接通过包管理器安装是最省事的方式。但不同发行版仓库里的版本差异比较大,这一点需要留意。
| 发行版 | 安装命令 | 仓库常见版本 | 备注 |
|---|---|---|---|
| CentOS 7 | yum install -y fio | 3.7左右 | 版本偏老,部分新参数不支持 |
| Rocky/Alma 8+ | dnf install -y fio | 3.19+ | 版本较新,日常够用 |
| Ubuntu 20.04 | apt install -y fio | 3.16 | 稳定版,功能完整 |
| Ubuntu 22.04 | apt install -y fio | 3.28 | 推荐,支持大部分新特性 |
| Debian 11/12 | apt install -y fio | 3.25/3.33 | 版本较新 |
安装完成后用fio --version确认版本号。如果你的测试需要用到较新的参数(比如--numa_cpu_nodes、--ssdplp等),而仓库版本太老,那就需要考虑源码编译。
2.2 源码编译的适用场景与步骤
什么时候需要源码编译?我总结了几种情况:仓库版本低于3.15、需要特定编译选项(比如启用libaio以外的IO引擎)、或者需要在非主流架构上运行。编译过程本身不复杂,但依赖包容易漏装。
# 安装编译依赖 yum install -y gcc make libaio-devel zlib-devel # 或者 Ubuntu/Debian apt install -y gcc make libaio-dev zlib1g-dev # 下载源码(以3.35为例) wget https://github.com/axboe/fio/archive/refs/tags/fio-3.35.tar.gz tar -xzf fio-3.35.tar.gz cd fio-fio-3.35 # 配置与编译 ./configure make -j$(nproc) make install编译完成后fio默认安装在/usr/local/bin/fio。这里有个容易忽略的细节:如果系统里同时存在包管理器安装的旧版本,which fio可能指向/usr/bin/fio,导致你以为在用新版本实际用的是旧的。用fio --version和which fio双重确认。
注意:源码编译时如果没装
libaio-devel,configure阶段不会报错,但编译出来的fio会缺少libaio引擎,跑测试时指定--ioengine=libaio会直接报错。这个坑我踩过,排查了半天才发现是依赖缺失。
2.3 验证安装与权限确认
安装完先跑一条最简单的命令验证:
fio --name=verify --filename=/tmp/fio_test --size=64M --rw=write --bs=4k --direct=1 --runtime=10 --time_based这条命令会在/tmp下创建一个64MB的文件做4K直接写入测试。如果报权限错误,检查当前用户对目标目录是否有写权限。如果报direct=1不支持,说明文件系统不支持O_DIRECT(比如tmpfs就不支持),换到ext4或xfs分区上测试。
另外提醒一点:测试裸盘(比如/dev/sdb)需要root权限,测试文件系统上的文件则普通用户即可。生产环境做裸盘测试前务必确认目标盘上没有数据,--filename指向错误的分区会导致数据丢失。
3. 理解fio的参数体系:从业务场景反推配置
3.1 四个核心维度决定测试模型
fio的参数有上百个,但决定测试模型本质的就四个维度:IO类型(读/写/混合)、IO大小(块大小)、IO模式(顺序/随机)、并发深度(队列深度和线程数)。把这四个维度想清楚,参数配置就完成了一大半。
IO类型由--rw控制。常见取值有read、write、randread、randwrite、randrw、rw。其中randrw和rw是混合读写,需要配合--rwmixread指定读的比例。比如数据库场景通常是randrw加--rwmixread=70,表示70%读30%写。
IO大小由--bs控制。4K对应数据库OLTP场景,8K/16K对应部分中间件,64K/128K对应大块顺序读写,1M以上对应视频流或备份场景。块大小直接决定了IOPS和带宽的换算关系:IOPS乘以块大小就是带宽。4K随机读跑出10万IOPS,换算带宽就是400MB/s左右。
IO模式由--rw中的rand前缀决定。顺序读写适合评估大文件传输和日志追加场景,随机读写适合评估数据库和虚拟化场景。这里有个常见误解:很多人觉得SSD不需要关心顺序还是随机,实际上SSD在顺序和随机下的性能差异虽然比机械盘小,但在高队列深度下依然明显。
并发深度由--iodepth和--numjobs共同决定。--iodepth是单个job的队列深度,--numjobs是并行job数量。总并发等于两者乘积(在同一个job共享文件的情况下)。机械盘队列深度超过32基本没有收益,NVMe盘可以跑到128甚至256。
3.2 常用参数速查与业务映射
下面这张表把常见业务场景和fio参数配置对应起来,可以直接参考:
| 业务场景 | rw | bs | iodepth | numjobs | 说明 |
|---|---|---|---|---|---|
| MySQL OLTP | randrw | 16k | 32 | 4 | rwmixread=70 |
| Redis AOF | write | 4k | 1 | 1 | 顺序追加 |
| 日志采集 | write | 64k | 16 | 2 | 顺序写 |
| 视频转码 | read | 1M | 8 | 1 | 大块顺序读 |
| 虚拟机镜像 | randrw | 4k | 64 | 8 | rwmixread=50 |
| 备份恢复 | write | 1M | 16 | 4 | 大块顺序写 |
这张表不是标准答案,而是一个起点。实际配置需要根据你的业务压测数据调整。比如MySQL的OLTP,真实场景下块大小可能是16K,但队列深度取决于连接数和存储类型,需要逐步调整找到拐点。
3.3 时间控制与结果稳定性
--runtime和--time_based是一对搭档。只写--runtime=60,fio会在完成指定size的数据量后停止,如果size设得小可能几秒就跑完了。加上--time_based后,fio会在指定时间内持续跑,size变成循环单位。做稳定性测试和稳态性能评估时,--time_based是必须的。
--ramp_time用来跳过预热阶段。SSD在刚写入时可能因为垃圾回收或缓存机制表现出虚高的性能,跑一段时间后才进入稳态。设置--ramp_time=30可以让fio先跑30秒预热,这30秒的数据不计入最终结果。我一般建议至少设10秒,对性能波动大的盘设30秒以上。
--group_reporting在多job场景下把结果合并输出,避免每个job单独打印一大段。做多线程测试时加上这个参数,输出会清爽很多。
4. 从零设计一个可信的测试方案
4.1 明确测试目标再动手
拿到一个磁盘性能测试需求,先别急着写命令。问清楚三个问题:测什么盘(机械/SSD/NVMe/网络存储)、模拟什么业务(数据库/文件服务/虚拟化)、关注什么指标(IOPS/带宽/延迟)。这三个问题的答案直接决定参数配置。
举个例子,如果目标是评估一块NVMe盘能否支撑某数据库的峰值负载,那测试模型应该是:随机读写混合、块大小16K、读比例70%、队列深度从32逐步加到256、多job并行。如果目标是评估一块SATA SSD作为日志盘的写入能力,那模型应该是:顺序写、块大小64K、队列深度16、单job或双job。
4.2 测试文件与裸盘的取舍
测试对象可以是文件,也可以是裸盘。两者各有适用场景。
测试文件的好处是安全、灵活、不需要root权限,适合开发环境和初步评估。但文件系统本身会引入额外开销(元数据、日志、缓存),测出来的结果不完全等于裸盘性能。测试文件时建议用--direct=1绕过页缓存,否则测的是内存速度而不是磁盘速度。
测试裸盘能获得最接近硬件的性能数据,适合生产环境评估和存储选型。但裸盘测试会破坏数据,必须确保目标盘无数据。另外裸盘测试需要root权限,且不能用--direct=0(裸盘没有文件系统缓存的概念)。
提示:如果非要在生产环境做裸盘测试,建议先用
lsblk和blkid确认目标盘没有挂载、没有文件系统、没有数据。我见过有人把/dev/sda当成数据盘测试,结果把系统盘写了,教训很深刻。
4.3 参数组合的逐步调优方法
一次性把所有参数设到最优是不现实的。我的做法是分步调优:先固定IO类型和块大小,从低队列深度开始跑,逐步增加队列深度,观察IOPS和延迟的变化曲线。当IOPS不再增长而延迟开始飙升时,那个拐点就是这块盘在当前场景下的最佳并发点。
具体操作上,可以用一个循环脚本自动跑多组参数:
for iodepth in 1 4 8 16 32 64 128; do fio --name=qd_test --filename=/data/testfile --size=4G \ --rw=randread --bs=4k --direct=1 --ioengine=libaio \ --iodepth=$iodepth --runtime=30 --time_based --ramp_time=10 \ --group_reporting --output-format=json \ --output=result_qd${iodepth}.json done跑完后从JSON结果里提取IOPS和延迟数据,画成曲线就能看到拐点。这种逐步调优的方法比一次性拍脑袋设参数靠谱得多。
4.4 测试时长与数据量的平衡
测试时长太短,结果受缓存和预热影响大;测试时长太长,浪费时间且可能影响生产。我的经验是:初步评估跑30秒到1分钟,正式评估跑3到5分钟,稳定性测试跑30分钟以上。数据量方面,测试文件大小至少要是磁盘容量的两倍以上,否则可能一直在同一区域读写,测不出全盘性能。
对于SSD,还要考虑写入放大和垃圾回收的影响。如果测试写入性能,建议先对盘做一次全盘写入(--rw=write --bs=1M跑满全盘),让盘进入稳态后再测。否则测出来的可能是出厂状态的虚高性能。
5. 结果解读:哪些数字真正值得看
5.1 输出字段的含义与优先级
fio的输出信息量很大,但真正需要关注的字段就那么几个。下面按优先级排列:
| 字段 | 含义 | 关注点 |
|---|---|---|
| iops | 每秒IO次数 | 随机读写场景的核心指标 |
| bw | 带宽 | 顺序读写场景的核心指标 |
| lat (avg) | 平均延迟 | 整体响应水平 |
| lat (p99) | 99分位延迟 | 长尾延迟,比平均值更重要 |
| clat | 完成延迟 | 不含排队时间的纯IO延迟 |
| slat | 提交延迟 | 提交IO到内核的时间 |
| cpu | CPU占用 | 评估测试本身的开销 |
很多人只看IOPS和带宽,忽略了延迟分布。实际上对于数据库和在线服务,p99延迟比平均IOPS更能反映用户体验。一块盘IOPS很高但p99延迟抖动大,实际业务表现可能很差。
5.2 延迟分布与长尾问题
fio的延迟统计默认输出平均值、标准差、最大值和分位数。分位数里p50、p90、p95、p99、p99.9都值得看。如果p99是平均值的5倍以上,说明存在明显的长尾延迟,可能是垃圾回收、队列拥塞或硬件问题导致的。
可以用--latency_percentiles参数自定义输出的分位数,比如--latency_percentiles=50,90,99,99.9,99.99。对于延迟敏感的业务,p99.9甚至p99.99才是真正需要关注的。
另外--latency_target和--latency_window可以设置延迟目标,fio会自动调整队列深度来满足延迟要求。这个功能在做SLA验证时很有用。
5.3 如何判断结果是否可信
拿到结果后先别急着下结论,做几个合理性检查。第一,看CPU占用。如果fio进程CPU占用接近100%,说明测试本身成了瓶颈,磁盘性能没被充分压出来。第二,看队列深度是否达到设定值。如果实际队列深度远低于设定值,说明IO提交速度跟不上。第三,看测试前后磁盘的SMART信息,确认没有异常。
还有一个简单的方法:用不同工具交叉验证。比如用dd做顺序写测试,用fio做随机读测试,如果两者结果差异巨大且无法解释,说明测试方法可能有问题。
6. 踩坑实录:那些让我重新跑测试的瞬间
6.1 页缓存导致的虚高结果
早期做文件测试时没加--direct=1,测出来顺序读带宽跑到好几GB/s,比盘的理论带宽还高。原因就是数据被页缓存了,fio读的是内存不是磁盘。加上--direct=1后结果直接掉到真实水平。这个坑很常见,尤其是测试小文件时,文件可能完全被缓存,测出来的结果毫无意义。
注意:
--direct=1在部分文件系统上可能不支持,比如tmpfs、overlayfs。如果报错,换到ext4或xfs分区测试,或者用--invalidate=1在每次IO前失效缓存(但效果不如direct)。
6.2 队列深度设置不当导致的误判
有一次评估一块NVMe盘,设了--iodepth=1跑随机读,结果IOPS只有几千,差点得出“这块盘不行”的结论。后来意识到NVMe的优势在于高队列深度,改成--iodepth=128后IOPS直接冲到几十万。队列深度和存储类型强相关,机械盘低队列深度就能跑满,NVMe需要高队列深度才能发挥。
6.3 测试文件大小与磁盘容量的关系
测试文件设得太小,比如在1TB盘上只测1GB文件,fio可能一直在同一区域读写,测出来的是局部性能而非全盘性能。尤其是机械盘,内外圈性能差异明显,只测外圈会高估整体性能。建议测试文件至少是磁盘容量的两倍,或者直接用裸盘测试。
6.4 多job共享文件的锁竞争
用--numjobs多job测试时,如果多个job操作同一个文件,默认会有文件锁竞争,导致结果异常。解决办法是每个job用独立的文件,通过--filename_format参数指定文件名格式,比如--filename_format='testfile.$jobnum'。或者用--stonewall让job串行执行,但这样就不是真正的并发了。
6.5 系统参数对测试结果的影响
Linux的IO调度器、预读参数、脏页回写策略都会影响fio结果。测试前建议确认IO调度器设置:NVMe盘用none,SSD用mq-deadline或none,机械盘用bfq或mq-deadline。可以用cat /sys/block/sdX/queue/scheduler查看,用echo修改。另外vm.dirty_ratio和vm.dirty_background_ratio在写入测试时影响很大,测试前最好确认这些参数符合预期。
7. 把fio用进日常运维的几个思路
fio不只是一个临时排查工具,完全可以纳入日常运维体系。我目前的做法是:新盘上线前跑一套标准测试,记录基线数据;业务变更后跑对比测试,确认性能没有退化;定期跑稳定性测试,观察性能衰减趋势。
具体实施上,可以把常用测试场景写成脚本,参数用变量控制,结果输出成JSON格式存档。这样每次测试的配置和结果都可追溯,方便对比。下面是一个简化的脚本框架:
#!/bin/bash TEST_DIR="/data/fio_test" RESULT_DIR="/var/log/fio_results" TIMESTAMP=$(date +%Y%m%d_%H%M%S) mkdir -p $RESULT_DIR run_test() { local name=$1 local rw=$2 local bs=$3 local iodepth=$4 local numjobs=$5 fio --name=$name \ --filename=$TEST_DIR/testfile \ --size=4G \ --rw=$rw \ --bs=$bs \ --direct=1 \ --ioengine=libaio \ --iodepth=$iodepth \ --numjobs=$numjobs \ --runtime=60 \ --time_based \ --ramp_time=10 \ --group_reporting \ --output-format=json \ --output=$RESULT_DIR/${name}_${TIMESTAMP}.json } run_test "randread_4k" "randread" "4k" 32 4 run_test "randwrite_4k" "randwrite" "4k" 32 4 run_test "seqread_1m" "read" "1m" 16 1 run_test "seqwrite_1m" "write" "1m" 16 1这个脚本跑完会生成四个JSON文件,分别对应随机读、随机写、顺序读、顺序写。后续可以用jq或Python解析这些JSON,提取关键指标做趋势分析。
另外提醒一点:测试目录最好单独挂载,避免测试产生的IO影响系统盘和其他业务。测试时间尽量选在业务低峰期,减少对生产的影响。如果必须在业务高峰期测试,务必控制测试强度和时长,并做好监控。
8. 几个容易被忽略的细节
--ioengine的选择值得单独说一下。Linux下常用的是libaio和io_uring。libaio是传统异步IO引擎,兼容性好但队列深度受限于内核参数。io_uring是较新的异步IO框架,性能更好但需要内核5.1以上。如果内核支持,优先用io_uring。可以用--ioengine=io_uring指定。
--numa_cpu_nodes和--numa_mem_policy在多路服务器上影响很大。如果测试进程和磁盘不在同一个NUMA节点,跨节点访问会引入额外延迟。可以用numactl --hardware查看拓扑,用fio的NUMA参数绑定到正确的节点。
--write_iops_log、--write_bw_log、--write_lat_log可以输出详细的时序日志,配合fio_generate_plots脚本能画出性能随时间变化的曲线。做深度分析时这些日志很有价值,能看到性能抖动和趋势变化。
最后说一个心态问题:fio的结果只是参考,不是绝对真理。真实业务的IO模式比fio模拟的复杂得多,fio测出来的数字好不代表业务一定流畅,数字差也不代表业务一定卡。把fio当作评估工具而不是判决工具,结合业务监控和实际体验综合判断,才是正确的用法。