news 2026/10/28 4:03:50

FIO磁盘性能测试实战:参数配方、报告解读与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FIO磁盘性能测试实战:参数配方、报告解读与避坑指南

三块新盘的验收报告躺在群里,跑分漂亮得很——磁盘性能测试工具 FIO打出来的 4K 随机读 30 万 IOPS,顺序读 6GB/s。结果业务上线两周后,报表任务从 40 分钟涨到一个半小时,慢查询日志一天翻了三倍。把 fio 重新拎出来压一遍才发现,那几块盘在混合小块随机读写的场景下直接掉到不足两万 IOPS,99 分位延迟从 300 微秒飙到 30 毫秒。同一块盘,前后两份报告差了十几倍,问题不在硬件,在测法。

这件事之后我把 fio 的用法彻底捋了一遍。它不是什么高深的工具,本质上就是一个"按你指定的方式,对指定位置反复发起 IO,并把结果统计成表格"的压测器。真正难的是怎么指定方式——块大小选错了、队列深度给少了、缓存没绕过去、跑的时间不够长,出来的数字就完全不能用来做决策。它适合三类人:做存储选型和验收的运维、给数据库挑盘的 DBA、以及写后端服务想知道自己 IO 模型会打成什么形状的开发。下面这些内容,就是我把这几年踩过的坑和稳定可复现的配置整理出来的完整版本。

1. 先弄清 fio 到底在测什么

很多人第一次用 fio,是照着某篇文章抄一条命令,跑完盯着iops=后面那个数字看,然后截图发群。这种用法十次有八次得出错误结论。原因很简单:磁盘性能从来不是一个标量,它是一条随负载形状变化的曲面。你得先知道自己在曲面的哪个点上,才知道那个数字有没有意义。

1.1 四个指标与它们之间的换算关系

fio 报告的核心指标就四个,剩下的都是衍生品。

  • IOPS:每秒完成的 IO 次数。小块随机场景的主角。
  • 带宽 BW:每秒搬运的数据量。大块顺序场景的主角。
  • 延迟:单次 IO 从提交到完成的时间,fio 会把提交延迟(slat)、完成延迟(clat)和总延迟(lat)分开统计。
  • 抖动:延迟的分布离散程度。fio 用百分位曲线和 stdev 来表达,这一项最容易被忽略,但往往是线上体验的真正杀手。

IOPS 和带宽之间有一个换算公式,随手就能算:带宽 = IOPS × 块大小。比如 4KiB 块、每秒 5 万次 IO,换算下来是50000 × 4KiB = 200000KiB ≈ 195MiB/s。这个换算在做容量规划的时候特别有用,因为磁盘厂商标称的是 IOPS,而业务方跟你提的需求往往是"我要每秒 300 兆的写入"。一块盘如果顺序写能到 500MiB/s,但在 4KiB 随机写下只有 2 万 IOPS,那么它的随机写带宽上限其实只有 78MiB/s 左右,差了六倍多。厂商宣传页上那个 500 是顺序大块下的数字,拿它去套数据库的写入需求,大概率要翻车。

延迟和队列深度之间也有一个硬关系,业内叫利特尔法则:IOPS = 队列深度 ÷ 平均延迟。反过来说,你想跑到某个 IOPS,需要的队列深度等于目标 IOPS × 单次延迟。这个公式后面第 4 章会展开算,它是你设置iodepth的唯一依据,拍脑袋填个 32 或者 128,出来的数据基本没有参考价值。

提示:延迟的单位极易看错。fio 的slat/clat/lat在输出里单位可能是nsec也可能是usec或msec,取决于数值大小,fio 会自动选一个合适的单位并打印在括号里。看到clat (usec): avg=79.34就是 79 微秒,看到clat (msec): avg=1.2就是 1.2 毫秒,两者差了一个数量级,做报告时看漏这个括号会闹大笑话。

1.2 fio 的作业模型与 job 文件长什么样

fio 的运行单位叫job。一个 job 就是一段独立的 IO 流定义,它有自己的读写模式、块大小、队列深度、目标文件或设备。你可以在一条命令里定义多个 job,也可以写进一个 job 文件里。多个 job 之间默认并行执行,也可以靠stonewall参数强行串行。

实际工作中我强烈建议用 job 文件而不是一长串命令行参数。原因有两个:一是命令行参数超过十几个之后根本没法审阅,同事接过你的命令看不出你到底测了什么;二是 job 文件可以提交进版本库,半年后回看还能复现。job 文件是 ini 风格的,分[global]全局段和具名 job 段,全局段的参数会被下面所有 job 继承。

[global] ioengine=libaio direct=1 time_based=1 runtime=300 ramp_time=60 group_reporting=1 filename=/data/fiotest/testfile size=40G [randread-4k] rw=randread bs=4k iodepth=32 numjobs=4 [seqwrite-1m] rw=write bs=1m iodepth=8 numjobs=2

上面这份文件定义了两个 job,它们共享全局段的引擎、缓存策略、运行时长等设置,只在读写模式、块大小和并发参数上分道扬镳。这样一份文件既能量出随机读的能力,也能顺手量出顺序写的上限,跑完一次 300 秒,两份数据都有了。

1.3 六个决定结果走向的核心参数

fio 的参数有上百个,但真正改变结论的就那么六个。把它们吃透,剩下的都是锦上添花。

ioengine决定 IO 以什么方式下发。Linux 上做磁盘压测基本只用libaio(内核原生异步 IO)或者更新的io_uring;psync、sync是同步引擎,队列深度永远上不去,只适合测单线程同步 IO 的场景;mmap走内存映射,容易把页缓存的影响混进来,一般不用。选错引擎,iodepth参数直接失效——用sync引擎填 128 的队列深度,实际队列深度还是 1。

direct决定是否绕过操作系统的页缓存。测物理介质必须设direct=1,否则你测的是内存读写速度,几 GB/s 的数字毫无意义。这个后面单独说。

bs是块大小,它直接决定你测的是哪一类负载。4K 对应数据库的页大小,8K 到 16K 是常见的文件系统块,128K 到 1M 是大数据与备份场景。块大小选错,等于跑错了赛道。

iodepth是单个 job 的队列深度,也就是同时有几个 IO 在路上。它决定了你能否把设备的并行能力压榨出来。消费级盘队列深度给到 32 就饱和了,企业级 NVMe 能吃掉 128 甚至 256。

numjobs是并发的 job 数量,每个 job 是独立进程或线程。总队列深度近似等于numjobs × iodepth。用多 job 模拟的是多线程应用的并发形态,比如数据库的连接池。

runtime与time_based一起决定跑多久。只写runtime不写time_based,fio 会认为这是一次性跑完指定数据量就结束,runtime只起超时保护作用。想跑固定时长,两个必须同时写。

2. 安装:从包管理器到源码编译

fio 的安装本身不复杂,但版本差异带来的坑不少,尤其是在老的服务器上。我遇到过最典型的一次,是某台机器上装的是好几年前的版本,ioengine=io_uring直接报"找不到引擎",排查了半天才发现是版本太老——那个引擎是后面才加进去的。

2.1 用包管理器装:快,但要确认版本

绝大多数主流 Linux 发行版都收录了 fio。Debian 系用apt,RedHat 系用dnf或yum:

# Debian / Ubuntu 系 sudo apt update sudo apt install -y fio # RedHat / CentOS / Rocky 系 sudo dnf install -y fio # 装完确认版本 fio --version

包管理器装的好处是省事,依赖自动解决,缺点是版本跟着发行版走。较新的发行版一般能拿到 3.x 的中后期版本,老发行版可能还停在 3.1 甚至 2.x。判断标准很简单:fio --enghelp能列出哪些引擎,fio --version看主版本号。3.0 以上基本够用,但如果你想用io_uring引擎,至少得是 3.16 之后的版本。

装完顺手确认一下引擎清单,这一步能省掉后面很多莫名其妙的报错:

fio --enghelp

输出里如果能看到libaio、io_uring、sync、psync、null这些,说明引擎是齐的。如果libaio不在列表里,说明编译时没找到异步 IO 的开发库,后面所有带队列深度的测试都会退化成同步模式,数据直接失真。

2.2 源码编译:为了新引擎和可控版本

需要io_uring、需要更细的统计输出、或者要在没有包仓库的内网机器上部署,就得自己编。过程不复杂,但依赖要装全:

# 编译工具与核心依赖 sudo dnf install -y gcc make libaio-devel zlib-devel # Debian 系对应 sudo apt install -y gcc make libaio-dev zlib-dev # 下载解压后进入目录 tar -xzf fio-3.35.tar.gz cd fio-3.35 # 配置。--prefix 指定安装路径,避免污染系统目录 ./configure --prefix=/usr/local/fio # 编译并安装 make -j"$(nproc)" sudo make install # 验证 /usr/local/fio/bin/fio --version

./configure那一步的输出值得认真看一眼,它会把检测到的引擎和功能列出来,形如:

libaio yes io_uring yes posix aio yes zlib yes

每一项是yes还是no直接决定了你能用哪些参数。libaio是no,那基本等于白编。zlib是no,就没法测压缩相关的负载。想要io_uring,除了 fio 本身的版本,还得确认内核版本够——uname -r至少是 5.1 以上,同时对端得装了liburing开发包。

注意:用--prefix装到非标准路径后,旧的fio可能还在PATH里。用which fio和fio --version双重确认你调的是新编的那个,而不是系统自带的旧版本。这个坑我在两台机器上各踩过一次,两次都是跑完发现结果对不上,回头才看到用的是旧二进制。

2.3 版本差异会带来哪些实际影响

说几个具体会影响到测试结果的差异点,方便你判断手里的版本够不够用。

较老的版本(3.0 之前)没有io_uring引擎,异步下发只能靠libaio,而libaio在一些较新的内核上对某些设备的表现不如io_uring稳定。百分位延迟的自定义列表参数percentile_list在 2.x 后期才有,老版本只能用默认的 1/5/10/20……那一串。JSON 输出格式在老版本里字段名和结构都不一样,如果你的自动化脚本要解析结果,版本不统一会直接报错。

还有一个隐蔽的差异:ramp_time的行为在早期版本里和time_based的配合有过变化,某些版本里写了ramp_time但没写time_based,预热阶段的数据仍然会被统计进去。稳妥的做法是永远两个一起写。

3. 开测前的准备:九成的错误数据出在这一步

工具装好了,参数也懂了,但直接开跑大概率还是错的。测磁盘这件事,真正决定数据可信度的不是 fio 的参数,而是跑之前的现场准备。我把它拆成四件事:选介质、绕缓存、跑稳态、挂监控。

3.1 测文件还是测裸设备

这是第一个岔路口,两条路差别很大。

测文件的意思是filename=/data/fiotest/testfile,fio 会在文件系统上创建一个指定大小的文件,在文件范围内发 IO。这种测法包含了文件系统本身的开销——元数据、extent 映射、日志写、块分配策略。如果你的业务是跑在文件系统上的(几乎所有应用都是),那这个数字更贴近真实体感。

测裸设备的意思是filename=/dev/nvme0n1,fio 直接对块设备扇区下 IO,完全跳过文件系统。数字会比文件模式高,因为它把文件系统的开销全砍掉了。它适合用来判断硬件的理论天花板,或者验证存储厂商给的规格书。

我的做法是两个都测:先测裸设备拿理论值,再测文件拿实际值,两者的差距就是文件系统带来的损耗。这个损耗在小块随机写场景下经常有 10% 到 30%,是个不能忽略的量。

注意:filename指向裸设备是直接擦写,没有任何二次确认。上一次我在一台测试机上把filename写成了系统盘对应的设备名,回车之后系统当场起不来。所以现在我的习惯是:测裸设备前,先用lsblk把设备名、容量、挂载点连起来看三遍,确认这块盘既没有挂载点,也没有分区表,跟系统盘八竿子打不着。有条件的,在跑之前加一个--readonly先做只读探测。

3.2 绕过缓存:direct=1到底绕过了什么

direct=1让 fio 用O_DIRECT打开文件,数据在用户态缓冲区和磁盘之间直接搬运,不经过操作系统的页缓存。不设这个参数,你测的是内存速度——现代服务器内存带宽动辄几十 GB/s,能得出一份漂亮到荒谬的报告。

但direct=1并不能绕过所有缓存层。磁盘自己的写缓存、RAID 卡的缓存、虚拟化层的缓存、分布式存储的副本写缓存,这些它都管不着。所以还经常配一个fsync参数:fsync=1表示每次 IO 完成后都下发一次 fsync,强制落盘。开了它数字会难看很多,但那才是真正"数据安全落地"的速度。

[global] direct=1 fsync=1

什么时候该开fsync?看你的业务有没有持久化要求。数据库的 WAL 写入、消息队列的持久化消息,这类场景测的时候必须开,否则测出来的高 IOPS 是假的。反过来,如果是缓存类服务、临时文件、视频流缓冲,开fsync反而是过度严格,测出来的数字会低估实际能力。

还有一个常被忽略的缓存形态:内存盘。有些机器把/tmp挂成了 tmpfs,或者有些人为了图快把测试文件放在/dev/shm下面。这两种情况下direct=1也没用,因为整个文件就在内存里。判断方法很简单,df -h看一下目标目录的文件系统类型,tmpfs一律不能用。

3.3 预热、填充与跑进稳态

磁盘性能不是恒定的。刚通电的 SSD,SLC 缓存是空的,写什么都飞快;跑满缓存之后速度断崖式下跌。机械盘刚访问时的寻道缓存状态,和跑了几分钟之后也不一样。所以必须让 fio 先跑一段"热身",等设备进入稳定状态之后再开始统计。这就是ramp_time的作用。

[global] time_based=1 runtime=600 ramp_time=120

上面这段的意思是:总共跑 600 秒,但前 120 秒的数据不进入统计,只统计后 480 秒。对于一个 480 秒的统计窗口,120 秒的预热是比较稳妥的比例。企业级 SSD 一般 60 秒能进稳态,消费级的盘可能要 180 秒以上。

文件模式下还有一个隐藏的时间成本:fio 第一次运行时需要创建并填满那个测试文件。如果size=40G,这个填充过程本身就要花几十秒甚至几分钟,而且这段时间的 IO 特征和后面完全不一样。更麻烦的是,如果你没设time_based,fio 跑完一个完整的 size 就结束了,可能填充刚结束测试也结束了,你拿到的是最不具代表性的一段。

我的做法是:先单独跑一次纯填充,把文件建好,再跑正式测试。填充用一个专门的 job,rw=write、bs=1m,反复重写几次,把设备的映射表和 GC 状态都推到稳定区。第二次跑正式测试时 fio 会发现文件已存在且大小正确,跳过填充直接开测。

3.4 测试期间必须挂的监控

只看 fio 自己报的数字,等于只看病人自己的描述,不做体检。至少要挂一个系统层监控做交叉验证。最轻量的是iostat:

iostat -x 1 5

重点看几列:r/s和w/s是实际的 IO 次数,应该和 fio 报的 IOPS 对得上;rkB/s和wkB/s是实际吞吐;await是平均等待时间,包括排队时间和实际服务时间;aqu-sz是平均队列长度,能反推实际生效的队列深度;%util是设备忙碌比例。

交叉验证的价值在于发现"测了个寂寞"的情况。比如 fio 报 20 万 IOPS,但iostat显示w/s只有 3 万,说明有大量 IO 被缓存吸收或者被合并了。再比如aqu-sz一直是 1,而你明明设了iodepth=64,说明队列深度根本没生效——大概率是 ioengine 选错了,退化成了同步模式。

另外,测试期间别跑其他重活。同机上的备份任务、日志轮转、监控采集频率过高,都会污染数据。我一般会在测试前把非必要的定时任务临时禁掉,测试期间只看监控不动手。

4. 四类典型场景的参数配方

参数不是拍脑袋定的,每一个都应该有计算依据。这一章把常见的四类负载拆开,从"要跑多少 IOPS"倒推"该给多少队列深度",给出可以直接抄的配方。

4.1 先把队列深度算出来:利特尔法则的实战用法

公式前面提过:队列深度 = 目标 IOPS × 单次 IO 的平均延迟。关键在于,单次延迟不是猜的,得先测。

做法是先用一个极小的队列深度(iodepth=1、numjobs=1)跑一轮随机读,拿到这个设备在无排队状态下的裸延迟。假设测出来 4KiB 随机读的平均clat是 200 微秒,也就是 0.0002 秒。现在你想知道"跑到 20 万 IOPS 需要多深的队列",套公式:

队列深度 = 200000 × 0.0002 = 40

所以iodepth设到 40 左右就够。设到 128 不会让 IOPS 变成三倍,只会让延迟变长、百分位变差——因为设备处理不过来,多余的在队列里排队。

反过来也可以验证设备的上限。假设厂商宣称某盘随机读能到 50 万 IOPS,而它标称的稳定延迟是 100 微秒,那需要的队列深度是500000 × 0.0001 = 50。如果你把iodepth设到 50 还跑不到 50 万,要么是延迟比标称的高,要么是有别的瓶颈(比如驱动、中断、或者你没开direct)。

再来看并发数。numjobs和iodepth的组合方式影响的是"并行形态"而不是单纯的"总数"。总队列深度约等于numjobs × iodepth。区别在于:numjobs=1, iodepth=64是单线程异步下发 64 个 IO,模拟的是一个高效的异步应用;numjobs=8, iodepth=8是 8 个线程各下发 8 个,模拟的是线程池形态。数据库这种多连接的应用,用后者更贴近。

场景建议 iodepth建议 numjobs说明
单线程同步应用探测11拿到最干净的裸延迟基线
数据库随机读写16 到 324 到 8总队列深度控制在 64 到 256
企业级 NVMe 打满64 到 1284总深度 256 到 512,接近硬件上限
顺序大块写4 到 81 到 2顺序场景对深度要求低

4.2 数据库型负载:小块随机混合读写

数据库的 IO 特征是:块小(4K 或 8K)、随机、读写混合、队列有一定深度。对应到 fio,就是rw=randrw加一个读写比例。

[global] ioengine=libaio direct=1 time_based=1 runtime=600 ramp_time=120 group_reporting=1 filename=/data/fiotest/dbfile size=40G [db-mix] rw=randrw rwmixread=70 bs=8k iodepth=32 numjobs=4

rwmixread=70表示 70% 读 30% 写。这个比例是从典型的 OLTP 场景里来的——查询远多于写入。如果你的业务是写密集的,比如日志收集、时序数据入库,比例要反过来调,rwmixread=20甚至10。

这里有个细节值得说:randrw模式下,fio 是每个 IO 独立掷骰子决定读还是写。较新的版本还提供了rwmixpattern参数,可以按固定节奏交替,比如读 7 个写 3 个循环。要不要用取决于你模拟的场景——真实业务里读写通常是混在一起无规律的,掷骰子更接近。

另一个高频误用是size设得太小。见过有人size=1G去测一块 4TB 的盘,跑出来的数字比厂商标称还高。原因很简单:1G 的范围太小,随机地址在这么小的空间里高度集中,SSD 内部的地址映射几乎全命中缓存,GC 压力也小。合理的取值是测试文件占设备可用容量的 25% 到 50%。太小失真,太大跑不完一个完整循环,而且时间成本高。

4.3 备份与迁移型负载:大块顺序读写

备份、数据迁移、大文件拷贝,这些场景的 IO 特征是块大、顺序、深度可以很低。测法完全不一样。

[global] ioengine=libaio direct=1 time_based=1 runtime=300 ramp_time=60 group_reporting=1 filename=/data/fiotest/seqfile size=60G [seq-write] rw=write bs=1m iodepth=8 numjobs=2

顺序场景下有两个陷阱。

第一是块大小要和实际业务对齐。备份软件常用的块大小是 128K 到 1M,如果你用 4K 去测顺序写,得出的带宽数字会明显偏低,因为它测的是"小块顺序"的性能,跟实际的大块搬运不是一回事。测之前最好抓一下真实业务的 IO 特征,实在抓不到就按 128K、512K、1M 各跑一遍,看曲线在哪一段平了。

第二是顺序写在 SSD 上其实是"伪顺序"。文件系统层面看是顺序追加,落到闪存颗粒上仍然是按通道、按 die 分散写。所以你会看到顺序写的带宽随着size增大逐步下降到某个稳定值——那才是真实值。这也是为什么ramp_time在顺序写场景下尤其重要。

还有一个容易混淆的点:rw=write是从零开始覆盖写,rw=rw是先读后写,rw=readwrite是先读后写交替。测备份恢复速度用write,测数据校验用read,测迁移工具的综合能力用readwrite。选错模式,数字差一倍不奇怪。

4.4 延迟敏感型负载:低并发下的纯探测

有些业务不看 IOPS,只看延迟。比如在线交易的数据库提交、实时风控的日志落盘,它们的并发度本来就不高,但要的是每一次 IO 都快且稳定。这时候用高队列深度去测,得出来的数字跟业务体感完全不搭。

[global] ioengine=libaio direct=1 time_based=1 runtime=300 ramp_time=30 group_reporting=1 filename=/data/fiotest/latfile size=20G rw=randread bs=4k iodepth=1 numjobs=1 percentile_list=50:90:99:99.9:99.99

iodepth=1、numjobs=1是纯粹的裸延迟探测。跑出来的clat平均值只是参考,真正要看的是百分位曲线。percentile_list参数可以自定义输出哪些百分位,默认那串可能不够细。99.9 分位和 99.99 分位的延迟如果比中位数高出一个数量级,说明设备存在偶发的长尾——这种情况在高并发下会被放大成慢请求,业务侧表现为"偶尔卡一下"。

我一般会跑三组对比:iodepth=1、iodepth=4、iodepth=16。三组的中位数延迟应该接近,但 99.9 分位会随深度快速上升。哪一档开始失控,就说明这个设备的"舒适区"在那一档以下。这个结论比任何单一数字都更有指导意义。

4.5 多盘与多机压测怎么组织

单机单盘测完,往往还要测整机或整个存储池。这时候有两个选择。

多盘并行用 job 文件里多个 job 段,每段指向不同的设备:

[disk1] filename=/dev/nvme1n1 rw=randread bs=4k iodepth=64 numjobs=2 [disk2] filename=/dev/nvme2n1 rw=randread bs=4k iodepth=64 numjobs=2

group_reporting=1会把结果按 job 分组汇总。要注意的是多个 job 同时砸盘会互相干扰,测出来的是"整机聚合能力",不是单盘能力。想看单盘数据就一个一个来。

多机压测用 fio 的客户端-服务端模式。在压测机上启动服务端:

fio --server --daemonize=/var/run/fio.pid

在控制机上指定目标并下发 job:

fio --client=target-host-1 --client=target-host-2 db-mix.fio

这个模式在测分布式存储或者共享存储池的时候特别有用,因为单个客户端节点可能压不满后端。多客户端同时打,才能看清后端聚合能力的真实上限。要注意所有客户端的 fio 版本尽量一致,版本不一致时握手会失败。

5. 输出怎么读:别只盯着 iops 那一行

fio 的默认文本输出信息量很大,但绝大多数人只看了前两行。真正有用的信息在后面。

5.1 逐行拆解一份典型报告

下面是一份 4K 随机读的典型输出(数字做过处理,只保留结构):

test: (groupid=0, jobs=4): err= 0: pid=12345: Mon Jan 1 10:00:00 2024 read: IOPS=199k, BW=777MiB/s (815MB/s)(20.0GiB/26371msec) slat (nsec): min=1200, max=88012, avg=2345.67, stdev=1102.33 clat (usec): min=32, max=4211, avg=79.34, stdev=41.22 lat (usec): min=34, max=4215, avg=81.70, stdev=41.30 clat percentiles (usec): | 1.00th=[ 40], 5.00th=[ 45], 10.00th=[ 48], 20.00th=[ 52], | 50.00th=[ 65], 90.00th=[ 103], 95.00th=[ 122], 99.00th=[ 178], | 99.90th=[ 372], 99.99th=[ 832] bw ( KiB/s): min=680000, max=820000, per=100.00%, avg=795000.00 iops : min=170000, max=205000, avg=198750.00 cpu : usr=12.34%, sys=28.76%, ctx=1023456, majf=0, minf=0 IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.2%, 32=1.3%, 64=98.2%, >=128=0.0% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% issued rwts: total=5242880, 0, 0, 0 short=0, 0, 0, 0 dropped=0, 0, 0, 0 latency (usec): target=0, window=0, percentile=100.00%, depth=64

逐块看:

第一行err= 0是错误计数。任何非零值都要立刻停下来查,说明有 IO 失败了,后面的数字全部不可信。

read: IOPS=199k, BW=777MiB/s是汇总值。注意括号里的20.0GiB/26371msec,这是总数据量和总耗时。用这个反算:20.0GiB / 26.371s ≈ 776MiB/s,和前面报的对得上,说明统计没出错。

slat/clat/lat三个延迟。slat是提交延迟,从 fio 决定发起到真正下发给内核的时间,一般是纳秒级,太大说明用户态卡住了。clat是完成延迟,从下发给内核到 IO 完成,这是真正反映设备能力的那一项。lat = slat + clat。看设备性能只看clat,被slat污染的总延迟会掩盖真实问题。

clat percentiles是延迟分布,第 5.2 节展开。

bw和iops的 min/max/avg反映的是运行期间的波动。如果 min 和 max 差了两倍以上,说明负载不稳定或者设备在热降速,只有 avg 是有误导性的。

IO depths这一行极其关键,它告诉你实际生效的队列深度分布。上面这份数据里 98.2% 的时间跑在深度 64,说明iodepth=64确实生效了。如果这一行显示 99% 集中在深度 1,那你设的iodepth就是摆设,多半是引擎选错了。

issued rwts里的short=和dropped=也必须是 0。short表示实际读写量少于请求量,dropped表示因为队列满被丢弃的 IO。两者非零都说明测试环境有问题。

cpu那一行里的sys=28.76%在压测高 IOPS 设备时经常成为瓶颈。如果 sys 跑到 90% 以上,你测的其实是 CPU 的下发能力,不是磁盘的能力。这时候要减少numjobs、简化统计项,或者换用开销更低的引擎。

5.2 百分位延迟比平均值重要得多

平均值是延迟统计里最会骗人的指标。一份数据里 99% 的 IO 用了 50 微秒,1% 的 IO 用了 50 毫秒,平均值算下来是 550 微秒——看起来还能接受,但那 1% 的慢请求在业务侧就是偶尔的超时、偶发的卡顿。

看百分位的正确姿势是找"拐点"。上面那份数据:50 分位 65 微秒,90 分位 103 微秒,99 分位 178 微秒,99.9 分位 372 微秒。从 50 到 99 分位涨了不到 3 倍,这属于健康的长尾。如果 99.9 分位突然跳到 5 毫秒甚至 20 毫秒,那就是典型的"长尾拖尾",通常意味着设备在做 GC、有坏块重映射、或者底层有别的租户在抢资源。

判断标准我给一个经验值:99.9 分位延迟不超过中位数的 10 倍,算健康。超过 20 倍就要查原因,超过 50 倍基本可以判定这块盘不适合延迟敏感业务。

还有一个小技巧:把stdev和中位数对比。上面clat的stdev=41.22微秒,中位数 65 微秒,stdev 是中位数的 63%,说明分布比较宽。如果 stdev 比中位数还大,那这条曲线的形状就很成问题了,平均值的参考价值接近于零。

5.3 判断这份数据是否可信的五个自查点

跑完一份数据,先别急着写报告,过一遍下面这几条。

自查项异常表现大概率原因
数值量级4K 随机读超过 100 万 IOPS走了页缓存,或测试文件在内存盘上
队列深度IO depths集中在 1ioengine 用了同步引擎,或 iodepth 未生效
系统开销sysCPU 超过 80%CPU 成为瓶颈,测的是下发能力
错误计数err/short/dropped非零IO 失败或队列溢出,数据作废
波动幅度bw 的 min 和 max 差 3 倍以上设备热降速或测试期间有其他负载干扰

再补一条:同一份配置至少跑三次。三次的 IOPS 差异在 5% 以内算稳定,超过 15% 说明环境有干扰,需要排查。我见过太多"一次定终身"的报告,第二次跑就复现不出来。

6. 常见问题与避坑实录

这一章是纯经验,都是文档里不会写、但实际会撞上的东西。

6.1 现象与原因速查表

现象排查方向处理方式
报错fio: engine libaio not loadable编译时缺 libaio 开发库装libaio-devel后重新编译,或改ioengine=psync应急
报错No such file or directoryfilename的父目录不存在先mkdir -p建好目录,fio 不会自动创建多级目录
跑起来立刻结束,只跑了几秒没写time_based,跑完 size 就停了加time_based=1
数字高得离谱走了缓存,或文件在 tmpfs 上加direct=1,并用df -h确认文件系统类型
数字低得离谱bs太小、iodepth没生效、盘上还有别的负载查IO depths分布,查iostat的aqu-sz
同一个配置两次结果差很远没预热,或测试期间有干扰加ramp_time,停掉同期任务
写到一半速度断崖下跌SSD 的 SLC 缓存耗尽,进入稳态写入这是真实表现,把runtime拉长让统计窗口覆盖稳态段
报错wrong number of jobsjob 文件里某个段缺了必填项检查每段是否有rw和bs
结果文件解析失败版本不一致导致 JSON 字段名不同统一各机器的 fio 版本

6.2 几个我自己踩过的坑

第一个坑:size的单位理解错了。fio 的size默认单位是字节,size=40G表示 40GiB。但如果你在多个 job 里都写了filename和size,每个 job 会各自创建一个 40G 的文件,四个 job 就是 160G。我曾经在一台只有 200G 空间的机器上写了numjobs=8、size=40G,跑到第四个 job 就报磁盘空间不足。要么用$jobnum变量让每个 job 指向不同文件,要么让它们共享同一个文件——后者需要所有 job 用同样的filename,并且 fio 会做文件锁处理。

第二个坑:--output和--output-format搞混了。--output=result.txt是把输出重定向到文件,格式还是文本;--output-format=json --output=result.json才是产出 JSON。两个参数经常被写成一个,结果得到一个后缀是 json 但内容是文本的文件,后面的 jq 解析全部报错。

第三个坑:在虚拟机和云盘上测出来的数字虚高。某次在一台云主机上测 4K 随机写,短时间跑出了非常漂亮的 IOPS,比同规格的物理机还高。后来把runtime从 60 秒拉到 600 秒,前 60 秒的高峰消失了——那是突发额度在起作用,额度用完之后回落到一个很平庸的水平。所以任何云上或者虚拟化环境的测试,runtime至少给到 300 秒以上,ramp_time给到 120 秒,让突发额度彻底消耗完再统计。

第四个坑:没注意 RAID 卡缓存策略。带缓存的 RAID 卡如果没开写穿模式,测出来的写性能其实是卡上内存的性能,比盘快一个数量级。做法是测试前确认卡的写策略,测"真实落盘"性能时切到写穿,测"业务实际感受"时保持回写但要知道数字里包含了缓存。

第五个坑:忘了看latency那一行。fio 支持用--latency_target做延迟控制,比如要求 99.9 分位的延迟不超过 1 毫秒。开了这个参数,fio 会动态调整队列深度来满足延迟目标。有一次同事抄了一份带这个参数的配置去测,结果 IOPS 数字比预期低很多,排查半天才发现是延迟目标卡住了队列深度。看到输出最后一行有latency (usec): target=...,就说明这个功能是开着的。

第六个坑:忘了清理测试文件。几十 G 的测试文件跑完不删,日积月累把盘占满了,然后某天监控报警。现在我的 job 文件里都会带一句注释,提醒自己在验收后删掉/data/fiotest/。或者干脆把测试目录单独挂一个小分区,跑完直接格式化。

最后分享一个小习惯:我现在所有正式验收都用同一套 job 文件模板,模板顶部固定写着这次测试的目的、设备型号、容量、文件系统、预热时长、跑了几轮。半年后翻出来,能立刻复现当时的结论,也能看出设备性能有没有随着使用出现衰减。fio 本身只是把尺子,真正有价值的是那把尺子上留下的刻度记录。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 4:03:16

MPC模型预测控制:原理与工程实践指南

1. MPC模型预测控制:从数学原理到工程实现在工业控制领域,模型预测控制(Model Predictive Control, MPC)早已从学术研究走向大规模工程应用。作为现代控制理论的重要分支,MPC以其处理多变量约束的天然优势,…

作者头像 李华
网站建设 2026/10/28 4:03:08

Caveman思维:用极简命令行降低心智税,远离过度设计

说实话,我第一次看到 “caveman” 这个词出现在技术讨论里时,第一反应是那个顶着乱发、围着兽皮的经典角色。但后来跟着老工程师跑了两个项目,才明白这个词在开发者圈子里早就不再是“原始人”的字面意思,而是一种刻意保留粗糙感的…

作者头像 李华
网站建设 2026/10/28 4:02:15

高级词汇 impeccable 怎么用?词源、搭配与雷区全解析

开头说实话,做英文内容这几年,我见过太多“背了等于没背”的单词,impeccable 绝对是其中最典型的一个。很多学员第一次见到它,眼睛会亮一下,因为这个词看起来就高级——长、圆润、带点法式优雅。可一旦真到自己开口写句…

作者头像 李华
网站建设 2026/10/28 4:01:19

论文查重免费网站怎么选?5个实测自查方法避坑指南

论文查重这件事,几乎每个写过毕业论文的人都绕不开。我见过太多同学在提交前一周才开始慌,到处搜“免费查重”,结果要么被引流到付费页面,要么把论文上传到不知名的小站,最后重复率没降下来,稿子反倒先泄露…

作者头像 李华
网站建设 2026/10/28 4:01:03

E2V线扫相机串口参数与CameraLink采集调试避坑

简介:面向工业机器视觉与自动化检测从业者的一份E2V高速相机参数与实操说明,聚焦UNIIQA系列(EV71YC1MCL2010-BA1)在2k像素、Full CameraLink、1010μm像元、最高100kHz条件下的配置与调试问题。文档围绕串口指令展开,涵…

作者头像 李华
网站建设 2026/10/28 4:00:52

impeccable用法详解:别再把它当perfect替身,掌握搭配与语用边界

最近这个词的出场率明显变高了。朋友圈里夸人审美,动不动就来一句 impeccable taste;工作邮件里夸客户体验,也能看到 impeccable service;就连有些人的简历自我评价,都写上了 "My work is impeccable"。词是…

作者头像 李华