☰
fio-2.2.10源码编译与存储性能测试实战指南
2026/10/10 10:32:51 网站建设 项目流程

简介:fio-2.2.10.tar.gz 是面向 Linux 系统管理员、测试工程师与存储性能调优人员的开源 I/O 基准测试工具源码包,对应《fio工具在Linux下的磁盘读写性能测试详解》一文,用于评估硬盘、SSD、网络存储等设备的读写性能,解决 IOPS、吞吐量、延迟等关键指标难以量化的问题。压缩包共 342 个文件,约 573KB,以 125 个 c 源文件与 146 个 h 头文件为主体,构成核心测试引擎;另有 38 个 fio 作业配置文件、makefile 与 configure 构建脚本、manpage 手册及 gpm 图形化前端相关文件,便于编译安装与自定义测试场景。资源已有 409 人学习下载,适合希望深入理解 fio 多线程并发、灵活工作负载与 CSV/JSON 报告输出机制的读者。通过阅读源码与配套配置示例,可掌握随机读写、顺序读写、混合读写等模式的参数组织方式,并借助错误处理与持久性测试逻辑,为数据库、虚拟化及大数据场景下的存储调优提供可复用的排错思路与验证依据。

1. 拿到 fio-2.2.10.tar.gz 之后:先别急着解压,搞清楚它能替你回答什么

如果你正在做存储性能测试,大概率绕不开一个场景:新上的 NVMe SSD 标称 3.5GB/s 顺序读,但业务侧反馈数据库写入延迟忽高忽低,你怀疑是 IO 调度器或者队列深度配置的问题,却拿不出可复现的数据。这时候 fio 就是那个能把「我觉得慢」变成「4K 随机写 QD32 下 iops 只有 12000,延迟 P99 超过 8ms」的工具。fio-2.2.10.tar.gz 是 fio 的一个源码发布包,解压后是一套完整的 C 代码工程,需要自己编译安装。它不依赖特定发行版,从 CentOS 6 到 Ubuntu 14.04 那个年代的生产环境都能跑,这也是很多老运维至今保留这个版本的原因——新版本在旧内核上编译经常缺依赖,而 2.2.10 的 configure 脚本对老系统的兼容性做得比较克制。适合谁用?一是需要给存储选型出报告的测试工程师,二是排查线上 IO 抖动的 SRE,三是自己攒了 NAS 想验证硬盘真实吞吐的折腾型玩家。接下来的内容按「编译出可用二进制 → 写 job 文件跑出可信数据 → 避开参数陷阱」这条线走,每一步都给出可抄的命令和参数解释。

2. 从源码包到可执行文件:编译参数与依赖处理

2.1 为什么选源码编译而不是直接 yum install

很多教程上来就yum install fio或者apt-get install fio,在 CentOS 7 之后的系统上确实省事。但 fio-2.2.10 这个版本对应的发行版仓库里,包管理器拉到的往往是打了大量 backport 补丁的版本,--output-format=json的字段结构可能和官方文档对不上,你照着网上的 jq 解析脚本跑会报 key 不存在。源码编译的好处是版本号锁死,fio --version输出就是fio-2.2.10,任何测试报告里写清楚版本,别人复现时不会因为版本差异扯皮。另一个现实原因是有些内网机器没有外网 yum 源,但允许上传 tar.gz 包,源码编译是唯一出路。

编译前先确认基础依赖。fio 的核心功能只依赖 libaio 和 pthread,但如果你想用--ioengine=libaio跑出真实异步 IO 性能,libaio 开发头文件必须装。常见做法是:

# CentOS / RHEL 系 yum install -y gcc make libaio-devel zlib-devel # Debian / Ubuntu 系 apt-get install -y gcc make libaio-dev zlib1g-dev

gcc和make不用解释。libaio-devel提供libaio.h和静态库,没有它 configure 会报libaio engine not enabled,最后你只能跑--ioengine=sync,测出来的 iops 会被系统调用开销拖低一大截。zlib-devel是为了支持 gzip 压缩的 job 文件,虽然用得少,但缺了它 configure 会警告,强迫症看着难受。

2.2 configure 阶段的关键开关

解压后进入目录,先跑./configure。默认配置会探测系统能力,但有几个开关建议显式指定:

tar -zxvf fio-2.2.10.tar.gz cd fio-2.2.10 ./configure --prefix=/usr/local/fio-2.2.10 --disable-native

--prefix把安装路径独立出来,不污染系统/usr/local/bin,以后想换版本直接改 PATH 就行。--disable-native这个参数值得说一下:默认情况下 fio 会针对编译机器的 CPU 架构做优化,生成-march=native的指令。如果你在 A 机器编译然后拷贝到 B 机器跑,B 机器 CPU 不支持某些指令集就会直接 illegal instruction 崩掉。关掉 native 优化,牺牲一点性能换可移植性,测试工具本身不应该成为变量。

configure 跑完会输出一张表,重点看这几行:

libaio yes posixaio yes linux native aio yes

如果libaio显示 no,回去检查开发包是否装对。linux native aio是内核级异步 IO,2.6 以后的内核都支持,显示 no 通常是因为头文件路径问题,可以手动指定--extra-cflags="-I/usr/include/libaio"再试。

2.3 make 与安装后的验证

make -j$(nproc) make install

-j$(nproc)用满 CPU 核数并行编译,fio 代码量不大,一般一两分钟完事。安装完把路径加进环境变量:

export PATH=/usr/local/fio-2.2.10/bin:$PATH fio --version

看到fio-2.2.10就说明二进制可用。这时候别急着跑测试,先执行fio --enghelp确认 libaio 引擎在列表里。如果只有sync和psync,说明编译时 libaio 没链接上,跑出来的数据不能反映真实异步性能。我一般还会跑一个 10 秒的空转测试确认工具本身没问题:

fio --name=smoke --ioengine=libaio --rw=randread --bs=4k --size=64m --runtime=10 --time_based --filename=/tmp/fio_test --direct=1

这条命令在/tmp下创建一个 64MB 文件做 4K 随机读,--direct=1绕过 page cache,--time_based让它在 10 秒内反复跑而不是跑完 64MB 就停。如果输出里ioengine=libaio且iodepth生效,说明环境就绪。

3. 写一份能说服人的 job 文件:参数含义与场景映射

3.1 job 文件的基本结构和必填项

fio 支持命令行直接传参,但参数一多就容易乱,正经测试都写 job 文件。一个最小可用的 job 文件长这样:

[global] ioengine=libaio direct=1 runtime=60 time_based=1 group_reporting=1 filename=/dev/sdb [4k-randread-qd32] rw=randread bs=4k iodepth=32

[global]段里的参数对所有 job 生效,[4k-randread-qd32]是 job 名,会出现在最终报告里。ioengine=libaio启用异步 IO,direct=1绕过缓存直接读盘,这两个组合是测真实设备性能的标配。runtime=60配合time_based=1表示跑满 60 秒,不管文件大小。group_reporting=1把多个 job 的结果合并输出,不然每个 job 单独一段读起来累。filename指向裸设备/dev/sdb时,fio 会直接对块设备发 IO,不需要文件系统层,测的是设备最原始的吞吐。注意:对裸设备写测试会破坏数据,生产环境务必确认盘是空的或者用--readonly参数。

3.2 块大小、队列深度和读写模式的组合逻辑

这三个参数决定了你测的是什么场景。bs=4k模拟数据库随机 IO,bs=128k或bs=1m模拟大文件顺序读写。iodepth是队列深度,机械盘一般设 1 到 4,SATA SSD 设 16 到 32,NVMe 可以拉到 128 甚至 256。rw有read、write、randread、randwrite、randrw等选项,randrw还要配rwmixread=70表示 70% 读 30% 写。

一个常见的误区是拿bs=4k iodepth=1去测 NVMe,然后抱怨 iops 只有几千。队列深度 1 意味着同一时刻只有一个 IO 在飞,NVMe 的高并发能力完全没发挥出来。正确的做法是先用iodepth=1测延迟基线,再逐步加到 32、128 看 iops 和延迟的拐点。我一般会写一组 job 文件批量跑:

[global] ioengine=libaio direct=1 runtime=30 time_based=1 filename=/dev/nvme0n1 group_reporting=1 [4k-randread-qd1] rw=randread bs=4k iodepth=1 [4k-randread-qd32] rw=randread bs=4k iodepth=32 [4k-randread-qd128] rw=randread bs=4k iodepth=128

跑完看输出里的iops和clat percentiles。如果 QD1 到 QD32 的 iops 翻了 20 倍但 P99 延迟只涨了 2 倍,说明设备并发能力强;如果 iops 没怎么涨延迟先爆了,说明设备内部队列已经饱和,再加深度只会让请求排队。

3.3 输出格式与数据提取

fio 默认输出人类可读的文本,但要做图表或者入库就得用 JSON:

fio --output-format=json --output=result.json 4k-randread.fio

--output把结果写到文件而不是 stdout。JSON 里关键字段的路径是jobs[].read.iops、jobs[].read.clat_ns.percentile["99.000000"]。注意 2.2.10 版本的 percentile key 是字符串形式的浮点数,用 jq 提取时要加引号:

jq '.jobs[0].read.iops' result.json jq '.jobs[0].read.clat_ns.percentile["99.000000"]' result.json

如果 jq 报Cannot index number with string,说明你的 fio 版本 percentile 结构不同,先jq '.jobs[0].read.clat_ns' result.json看一眼实际结构再写提取表达式。

4. 避坑与排查:那些让测试结果不可信的细节

4.1 现象:iops 高得离谱,接近内存速度

原因:direct=1没生效或者被覆盖。常见情况是 job 文件里[global]段写了direct=1,但某个 job 段里又写了direct=0,后者覆盖前者。另一个可能是filename指向了一个已经存在的文件,而文件系统有缓存,fio 的 direct IO 在某些文件系统上(如 ext4 的某些挂载选项)会被降级为 buffered IO。

解决:跑测试前用fio --showcmd jobfile确认最终生效的参数,输出里会明确列出direct=1。另外用iostat -x 1在另一个终端观察,如果%util很低但 fio 报的 iops 很高,基本可以确定是缓存命中。

4.2 现象:测试跑了一半报Input/output error

原因:对裸设备测试时,filename指向的分区大小小于size参数指定的值,或者设备有坏块。2.2.10 版本对这种情况的错误提示不够友好,经常只报 IO error 不说是哪个 offset。

解决:先用blockdev --getsize64 /dev/sdb确认设备容量,size设成容量的 80% 左右留余量。如果是坏块,用badblocks -sv /dev/sdb扫一遍。测试裸设备时加--readonly避免误写。

4.3 现象:多 job 并行时结果互相干扰

原因:多个 job 同时写同一个文件或设备,IO 互相竞争,测出来的延迟是叠加后的结果。group_reporting=1只是合并显示,不隔离资源。

解决:要么用numjobs参数让 fio 自己管理并发(每个 job 独立文件),要么用cgroup隔离。我一般用numjobs=4配合filename_format让每个 job 写不同文件:

[global] directory=/mnt/test filename_format=fio_job_$jobnum numjobs=4

这样四个 job 分别写fio_job_1到fio_job_4,互不干扰。

4.4 现象:--time_based下 runtime 到了但测试没停

原因:runtime只对time_based=1生效,如果漏了这个参数,fio 会跑完size指定的数据量才停。另一个可能是ramp_time设得太长,实际测试时间被压缩。

解决:检查 job 文件里time_based=1和runtime是否在同一个段里。ramp_time一般设 5 到 10 秒让设备预热,不要超过 runtime 的 20%。

4.5 现象:编译时报undefined reference to 'io_setup'

原因:libaio 库链接顺序问题,或者-laio没加到 LDFLAGS。2.2.10 的 configure 脚本在某些发行版上探测 libaio 会误判。

解决:手动指定库路径重新 configure:

./configure --extra-cflags="-I/usr/include" --extra-ldflags="-L/usr/lib64 -laio"

如果还不行,检查/usr/lib64/libaio.so是否存在,不存在就重新安装libaio-devel。

5. 用 fio 做盘间对比与长期监控的一个小技巧

跑单次测试只能拿到一个时间点的数据,但存储设备的性能会随温度、磨损、后台任务波动。我习惯用 fio 的--write_bw_log和--write_lat_log参数把每次测试的带宽和延迟按时间序列落盘,然后用脚本画趋势图。具体做法是在 job 文件里加:

[global] write_bw_log=bench write_lat_log=bench log_avg_msec=1000

write_bw_log=bench会生成bench_bw.log,每行是时间戳和当时的带宽。log_avg_msec=1000表示每秒聚合一次,避免日志文件过大。跑完之后用 awk 快速看趋势:

awk '{print $1, $2/1024}' bench_bw.log | head -20

第一列是毫秒时间戳,第二列是 KB/s 单位的带宽。如果发现带宽在前 10 秒很高然后掉到一半,说明设备有 SLC 缓存,缓存写满后掉速,这个拐点对选型很重要。

另一个技巧是对比两块盘时用同一份 job 文件,只改filename,其他参数完全不动。跑完把 JSON 结果丢给 jq 提取关键指标做成表格:

指标盘 A (NVMe)盘 B (SATA SSD)
4K 随机读 QD1 iops150008000
4K 随机读 QD32 iops42000095000
4K 随机读 QD32 P99 延迟1.2ms8.5ms
128K 顺序写带宽2100MB/s520MB/s

这张表比任何文字描述都有说服力。注意 fio 2.2.10 的 JSON 里延迟单位是纳秒,做表时记得除以 1e6 转成毫秒。

从那以后我每次做存储测试都强制走一遍「先 QD1 测延迟基线,再梯度加深度找拐点,最后用日志看稳定性」的流程,再也没出现过报告被质疑「数据不可复现」的情况。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询