1. 磁盘性能测试的底层逻辑与工具选型
1.1 为什么磁盘性能测试总在“打架”
做存储和运维的人都有一个共同的痛:同一块盘,用不同工具跑出来的数字能差出好几倍。有人拿fio跑出50万IOPS,换vdbench一测只剩20万,然后就开始怀疑人生——到底是盘不行,还是工具在骗我?
这个问题的根源在于,磁盘性能测试从来不是“跑个分”那么简单。它本质上是在模拟特定业务场景下的IO负载,而不同工具对IO行为的建模方式、参数默认值、统计口径都不一样。你测的是4K随机读,但队列深度设了1,那测出来的就是单线程延迟,跟IOPS峰值没有半毛钱关系。
我在实际项目中见过太多这样的案例:采购了一批NVMe SSD,验收时用fio默认参数跑,数据漂亮得不行,结果上线后数据库该卡还是卡。后来一查,业务侧是128K顺序写为主,而验收测的是4K随机读——完全测错了方向。
所以这篇文章不打算给你一个“哪个工具更好”的简单结论,而是把vdbench和fio这两个主流工具拆开揉碎,从IO引擎、参数模型、统计方式到实战场景,一层层讲清楚它们各自的脾气和适用边界。看完之后,你至少能做到:拿到一块盘,知道该用哪个工具、怎么配参数、怎么看结果,而不是被数字牵着鼻子走。
1.2 两个工具的出身决定了它们的性格
vdbench是Oracle出品的存储性能验证工具,最早是为了测试Oracle数据库在各类存储上的表现而开发的。它的基因里带着强烈的“企业级存储验证”色彩——支持多主机并发、文件系统和裸设备、丰富的报告维度,而且自带数据校验功能。你可以把它理解成一个“存储验收专家”,适合做大规模、长时间、多节点的稳定性验证。
fio则是 Jens Axboe 大神开发的IO测试工具,这位同时也是Linux内核IO子系统的维护者。fio的设计哲学是“灵活到极致”,它几乎能模拟任何你能想到的IO模式,从简单的顺序读到复杂的混合随机读写、从单线程到多线程多设备并发。它的参数多到令人发指,但也正因为如此,fio成了事实上的行业标准——几乎所有存储厂商的规格书里写的IOPS,都是用fio跑出来的。
打个比方:vdbench像是一辆调校好的工程车,开箱即用,适合拉货跑长途;fio像是一套乐高积木,什么都能拼,但你得知道自己要拼什么。选哪个,取决于你的场景和目的。
1.3 选型前必须想清楚的三个问题
在决定用哪个工具之前,先问自己三个问题:
第一,你测的是裸设备还是文件系统?裸设备测试绕过了文件系统层,测的是块设备的原始性能;文件系统测试则包含了元数据操作、缓存、日志等开销。vdbench对两者的支持都很完善,fio则需要通过filename参数指定文件路径或设备路径来区分。
第二,你需要多主机并发吗?如果你的存储是共享的SAN或NAS,需要验证多客户端同时访问时的性能表现,vdbench的多主机编排能力是碾压级的。fio虽然也能多机跑,但需要自己写脚本协调,麻烦不少。
第三,你需要数据一致性校验吗?vdbench内置了数据校验功能,可以在读写过程中验证数据完整性,这对存储稳定性验证非常关键。fio也有verify选项,但配置起来相对繁琐。
这三个问题想清楚了,工具选型基本就定了。下面我们进入实战环节。
2. fio核心参数拆解与IO引擎深度解析
2.1 IO引擎:fio的灵魂所在
fio最核心的概念就是IO引擎(ioengine),它决定了fio用什么方式向操作系统提交IO请求。不同的引擎走的内核路径完全不同,性能表现也天差地别。选错引擎,测出来的数据毫无参考价值。
目前最常用的几种引擎:
| 引擎名称 | 工作方式 | 适用场景 | 注意事项 |
|---|---|---|---|
| sync | 同步IO,read/write系统调用 | 单线程低并发场景 | 性能最差,仅用于基线对比 |
| libaio | Linux原生异步IO | 高并发裸设备/文件测试 | 需要内核支持,队列深度才能生效 |
| io_uring | 新一代异步IO接口 | 高并发低延迟场景 | 需要较新内核(5.1+) |
| posixaio | POSIX异步IO | 跨平台兼容场景 | Linux上性能不如libaio |
| mmap | 内存映射方式 | 特定缓存测试 | 容易受页缓存影响 |
我个人的经验是:在Linux上测块设备性能,libaio是默认首选,除非内核版本足够新且你想压榨极致性能,那就上io_uring。sync引擎只在你想知道“最差情况”时才有用。
这里有个坑要特别注意:libaio的队列深度(iodepth)必须大于1才能真正发挥异步优势。如果你设了ioengine=libaio但iodepth=1,那它实际上退化成了同步IO,性能数据会非常难看。我见过不止一个人踩这个坑,然后得出“这块盘不行”的错误结论。
2.2 那些你必须搞懂的fio参数
fio的参数有上百个,但真正影响测试结果的核心参数就那么十几个。我把它们分成四类来讲:
IO模式类:
rw:读写模式,可选read、write、randread、randwrite、randrw、readwrite等。这是最基础的参数,决定了IO的方向和随机性。bs:块大小,支持4k、8k、64k、1m等单位。块大小直接决定了IOPS和带宽的换算关系。rwmixread:混合读写时的读比例,比如rw=randrw配合rwmixread=70表示70%读30%写。
并发类:
iodepth:队列深度,即同时提交的IO请求数。这是影响IOPS最关键的参数之一。numjobs:并发线程数或进程数。每个job独立运行,可以理解为模拟多少个客户端。thread:让numjobs以线程而非进程方式运行,减少上下文切换开销。
目标类:
filename:测试目标,可以是设备路径(如/dev/nvme0n1)或文件路径。size:每个job的IO总量,影响测试持续时间。runtime:测试运行时间,配合time_based使用。direct:是否绕过页缓存,1表示直接IO,0表示走缓存。测真实磁盘性能必须设direct=1。
输出类:
output:结果输出到文件。output-format:输出格式,可选normal、json、json+、terse等。json格式方便后续用脚本解析。
一个典型的4K随机读测试命令长这样:
fio --name=randread --ioengine=libaio --direct=1 \ --bs=4k --rw=randread --iodepth=32 --numjobs=4 \ --filename=/dev/nvme0n1 --size=10G --runtime=60 \ --time_based --group_reporting --output-format=json \ --output=result.json这条命令的意思是:用libaio引擎,直接IO,4K随机读,队列深度32,4个并发job,总共等效队列深度128,跑60秒,结果输出为JSON。
2.3 队列深度与IOPS的数学关系
很多人搞不清楚iodepth和numjobs的关系。简单说:总队列深度 = iodepth × numjobs。但这里有个前提——只有异步引擎才能让iodepth真正生效。
IOPS的理论上限可以用一个公式估算:
IOPS = 总队列深度 / 平均延迟
比如你的盘平均延迟是100微秒,总队列深度128,那理论IOPS = 128 / 0.0001 = 1,280,000。当然这是理想值,实际受限于设备控制器、总线带宽等因素。
反过来,如果你知道盘的标称IOPS和延迟,也可以反推需要多大的队列深度才能跑满:
所需队列深度 = IOPS × 延迟
假设盘标称500K IOPS,延迟80微秒,那所需队列深度 = 500000 × 0.00008 = 40。也就是说,iodepth至少要到40才能跑出标称性能。
这个计算在实际调参时非常有用。我经常用它来快速判断:当前测出来的IOPS偏低,到底是盘的问题还是队列深度不够。
2.4 一个容易被忽略的细节:数据布局
fio默认会在测试文件或设备上顺序写入数据,但如果你反复测试同一块盘,之前的数据布局会影响后续测试结果。特别是对SSD来说,FTL(闪存转换层)的映射表状态会直接影响写入性能。
我的做法是:每次测试前先用blkdiscard(对SSD)或写零操作把盘恢复到干净状态。对于机械盘,至少也要保证测试区域是连续的。这个步骤看起来不起眼,但能让你的测试结果可复现性大幅提升。
另外,size参数不要设得比实际可用空间还大,否则fio会报错。一般建议设为设备容量的50%-80%,留出足够的空间给FTL做垃圾回收。
3. vdbench实战:从单机验证到多机并发
3.1 vdbench的工作模型
vdbench的使用方式和fio截然不同。它不是通过命令行参数直接定义负载,而是通过一个参数文件(parameter file)来描述整个测试场景。这个文件里定义了SD(Storage Definition)、WD(Workload Definition)、RD(Run Definition)三个核心部分。
- SD:定义存储目标,可以是裸设备、文件系统路径、或网络共享。
- WD:定义工作负载,包括IO模式、块大小、读写比例、线程数等。
- RD:定义运行规则,包括运行时间、预热时间、多主机配置等。
这种分层设计的好处是清晰、可复用。你可以把SD和WD分开维护,不同的RD组合出不同的测试场景。
一个最简单的vdbench参数文件:
sd=sd1, lun=/dev/nvme0n1, threads=8 wd=wd1, sd=sd1, rdpct=100, seekpct=100, xfersize=4k rd=rd1, wd=wd1, iorate=max, elapsed=60, interval=1这段配置的意思是:定义一个名为sd1的存储目标,8个线程;工作负载wd1是100%随机读,4K块大小;运行60秒,每秒报告一次。
3.2 vdbench的独门绝技:数据校验
vdbench最让我放心的一点是它的数据校验功能。在参数文件中加上validate=yes,它会在写入数据时生成校验信息,读取时验证数据是否一致。这对于验证存储系统的可靠性非常关键。
我经历过一次存储控制器固件bug导致的数据静默损坏,就是用vdbench的校验功能发现的。当时fio跑了几小时都没报错,但vdbench一跑就报数据不匹配。后来厂商确认是固件问题,更新后解决。如果没有vdbench的校验,这种问题可能要等到业务数据出错才会被发现。
配置校验的写法:
wd=wd1, sd=sd1, rdpct=50, whpct=50, xfersize=4k, seekpct=100, validate=yes注意:开启校验后性能会有所下降,因为多了校验信息的读写开销。所以做纯性能测试时可以关掉,做稳定性验证时再打开。
3.3 多主机并发测试的配置方法
vdbench的多主机能力是它区别于fio的最大优势。配置方式是在参数文件中指定多个主机,每个主机上运行vdbench的从进程(slave),由主进程统一调度。
主参数文件:
hd=default, vdbench=vdbench, user=root, shell=ssh hd=host1, system=192.168.1.101 hd=host2, system=192.168.1.102 hd=host3, system=192.168.1.103 sd=sd1, lun=/dev/nvme0n1, threads=8, host=host1 sd=sd2, lun=/dev/nvme0n1, threads=8, host=host2 sd=sd3, lun=/dev/nvme0n1, threads=8, host=host3 wd=wd1, sd=sd1,sd2,sd3, rdpct=100, seekpct=100, xfersize=4k rd=rd1, wd=wd1, iorate=max, elapsed=300, interval=5这个配置会在三台主机上同时发起4K随机读,vdbench会自动汇总所有主机的性能数据。注意hd定义中的shell=ssh表示通过SSH连接从机,需要提前配置好免密登录。
多机测试时有个关键点:确保所有主机的时钟同步。否则汇总报告的时间戳会对不齐,影响分析。我一般会提前用NTP把所有节点的时间校准一遍。
3.4 vdbench报告解读要点
vdbench的输出报告非常详细,但信息量大也意味着容易看花眼。我一般重点关注这几个指标:
- rate:每秒完成的IO次数,即IOPS。
- resp:平均响应时间,单位毫秒。
- cpu:各主机的CPU占用率。
- MB/sec:吞吐量。
报告会按时间间隔(interval)逐行输出,最后给出汇总。看报告时要注意区分“总速率”和“单主机速率”。在多机测试中,总速率是所有主机之和,但单主机的表现可能差异很大。如果某台主机的速率明显偏低,可能是网络瓶颈或配置不一致导致的。
另外,vdbench的响应时间统计包含了队列等待时间,所以高队列深度下响应时间会自然升高。不要看到响应时间从0.1ms涨到2ms就慌了,先看看队列深度是多少。
4. 实战对比:同一块盘,两个工具跑出不同结果怎么办
4.1 对比测试的设计原则
要公平对比vdbench和fio,必须保证测试条件一致。我总结了几个必须对齐的维度:
| 对比维度 | fio参数 | vdbench参数 | 说明 |
|---|---|---|---|
| 块大小 | bs=4k | xfersize=4k | 必须一致 |
| 读写比例 | rw=randread | rdpct=100,seekpct=100 | 随机读 |
| 队列深度 | iodepth×numjobs | threads | 等效并发数 |
| 直接IO | direct=1 | openflags=o_direct | 绕过缓存 |
| 运行时间 | runtime=60 | elapsed=60 | 相同 |
| 预热 | 无内置 | 可设warmup | vdbench更优 |
我一般会先用fio跑一轮,记录IOPS、带宽、延迟;然后用vdbench跑相同条件的测试,对比结果。如果差异在5%以内,说明两个工具的一致性很好;如果差异超过10%,就需要排查原因了。
4.2 差异来源排查清单
实测中两个工具跑出不同结果是常态,差异来源主要有以下几个:
第一,队列深度的计算方式不同。fio的总队列深度是iodepth×numjobs,但vdbench的threads是每个SD的线程数,实际并发可能受限于iorate参数。如果iorate=max,vdbench会尽可能快地提交IO,但具体并发数取决于线程调度。
第二,统计口径不同。fio的IOPS统计包含了所有完成的IO,而vdbench可能会排除某些异常值。另外,fio的延迟统计默认包含队列等待时间,vdbench的resp也是。
第三,数据布局影响。如果两次测试之间没有清理盘,之前的数据分布会影响FTL状态,导致性能差异。这个在SSD上尤其明显。
第四,CPU亲和性。fio可以通过cpus_allowed绑定CPU,vdbench没有这个功能。在高IOPS场景下,CPU调度可能成为瓶颈。
我的建议是:不要纠结于两个工具的数字是否完全一致,而是关注它们在同一条件下的相对表现。比如你要对比两块盘的性能,那就用同一个工具、同一套参数去测,这样才有可比性。
4.3 一个真实的对比案例
去年我帮一个客户验收一批企业级NVMe SSD,标称4K随机读IOPS 800K。我先用fio跑:
fio --name=test --ioengine=libaio --direct=1 --bs=4k \ --rw=randread --iodepth=64 --numjobs=8 --filename=/dev/nvme0n1 \ --size=50G --runtime=120 --time_based --group_reporting结果:IOPS约620K,延迟约0.82ms。
然后用vdbench跑等效配置:
sd=sd1, lun=/dev/nvme0n1, threads=512, openflags=o_direct wd=wd1, sd=sd1, rdpct=100, seekpct=100, xfersize=4k rd=rd1, wd=wd1, iorate=max, elapsed=120, interval=5, warmup=30结果:IOPS约680K,延迟约0.75ms。
差异约9%。排查后发现两个原因:一是vdbench有30秒预热,盘进入了更稳定的状态;二是fio的numjobs=8产生了额外的进程调度开销。后来我把fio改成numjobs=1, iodepth=512,IOPS提升到了670K,差距缩小到1.5%。
这个案例说明:参数配置的细微差异会导致结果显著不同,对比测试时必须严格控制变量。
5. 常见问题与排查技巧实录
5.1 fio测试结果异常排查
问题一:IOPS远低于预期。
排查顺序:先确认direct=1是否设置,如果走了页缓存,测的是内存速度而非磁盘速度;再检查iodepth是否足够大,用前面讲的公式估算所需队列深度;然后确认ioengine是否选对,sync引擎在高并发下会成为瓶颈;最后检查CPU占用率,如果某个核跑满了,说明CPU是瓶颈。
问题二:延迟波动很大。
可能原因包括:后台有其他进程在抢IO、SSD正在做垃圾回收、测试文件碎片化严重。解决方法:测试前清理盘、关闭不必要的后台服务、用blkdiscard重置SSD状态。
问题三:读写混合测试时结果不稳定。
检查rwmixread是否设置合理,以及randrw模式下读写是否真的随机分布。有时候fio的随机数生成器会成为瓶颈,可以尝试randrepeat=0让每次运行的随机序列不同。
5.2 vdbench常见报错与解决
报错一:Cannot open /dev/xxx。
通常是权限问题。vdbench需要root权限才能直接访问块设备。确认用root运行,或者检查设备文件的权限。
报错二:多机测试时从机连接失败。
检查SSH免密登录是否配置正确,防火墙是否放行了vdbench使用的端口(默认5570)。另外确认所有主机上的vdbench版本一致,版本不一致可能导致协议不兼容。
报错三:数据校验失败。
如果开启了validate=yes且报数据不匹配,首先排除是测试配置问题(比如多个job写同一区域)。如果配置没问题,那很可能是存储系统的问题,建议联系厂商排查。
5.3 工具选择的经验法则
跑了这么多年测试,我总结了一个简单的选择逻辑:
- 快速验证、参数调优、厂商规格对比→ 用fio。灵活、快、行业标准。
- 存储验收、稳定性验证、多机并发→ 用vdbench。全面、可靠、报告清晰。
- 两者都用→ 重要项目建议两个工具交叉验证,结果一致才放心。
注意:无论用哪个工具,测试前一定要备份数据。直接IO操作会覆盖设备上的原有数据,且不可恢复。
5.4 性能测试的“三不原则”
最后分享三条我在实践中总结的原则:
不测无预热的数据。任何存储设备在冷启动和热稳定状态下的性能都不一样。至少预热30秒,等性能曲线平稳后再取数据。
不只看峰值。峰值IOPS好看但不代表实际体验好。关注P99延迟、性能一致性(IOPS波动范围)这些更能反映真实体验的指标。
不忽略环境差异。同样的盘插在不同服务器上、走不同的PCIe通道、配不同的BIOS设置,性能都可能不同。测试报告里一定要记录完整的硬件和软件环境信息。
这三条看起来简单,但真正做到的人不多。我见过太多测试报告只写了一个IOPS数字,没有任何上下文,这种数据拿来做决策是很危险的。
实际项目中,我通常会跑三轮测试:第一轮摸底,找到大致性能范围;第二轮调参,优化队列深度和并发数;第三轮验证,用最优参数跑长时间稳定性测试。三轮下来,对这块盘的脾气就摸得差不多了。这个过程比单纯跑一个命令要费时间,但得到的结论可靠得多。