
有没有遇到过这种情况一台 Linux 服务器用得好好的突然某个服务变慢了排查一圈发现 CPU 和内存都正常最后定位到磁盘。或者你刚买了一块 NVMe 固态想在装机后看看真实的读写能力是不是和包装盒上标的一致。再或者你准备上线一套数据库想评估底层存储扛不扛得住业务负载。这些场景都需要一个基本功Linux 下测试磁盘读写能力。干这行时间久了你会发现网上关于磁盘测试的教程很多但大多是复制粘贴一两条dd命令就草草收场。真正到了生产环境或者要做性能对比选型的时候那几条命令完全不够用。这篇文章我从实际运维和测试的角度把磁盘读写测试这件事从头到尾梳理一遍先搞清楚要测什么再说工具怎么选接着讲影响测试结果的底层机制最后给出一套能直接复现的完整操作。不管你是运维、后端开发还是自己折腾 NAS 和家庭服务器照着这篇文章的思路去测结果基本不会走偏。1. 先搞清楚你到底要给磁盘测什么很多人一上来就敲命令结果测出来的数据四六不靠。我先泼一盆冷水磁盘读写能力不是单一数字它是一组不同维度指标的合集。你要先想清楚自己的场景再去选合适的测试方法。1.1 磁盘读写能力是一块试金石磁盘读写能力说白了就是存储系统能跑多快、扛多少并发。日常业务中它直接决定了数据库查询快不快、日志能不能及时落盘、文件服务传输稳不稳。这块“试金石”主要拆成三个维度第一个维度是顺序读写带宽单位通常是 MB/s。它衡量的是磁盘一次性处理大块连续数据的能力典型场景是视频监控录像、数据库备份恢复、大数据批处理、文件拷贝。机械硬盘顺序读能到 150MB/s 到 200MB/s 已经算不错SATA 固态一般在 500MB/s 左右NVMe 固态动辄 3000MB/s 以上。第二个维度是随机读写 IOPS单位是次/秒。它衡量的是磁盘处理小数据块随机访问的能力典型场景是数据库 OLTP 业务、虚拟机磁盘、消息队列。IOPS 才是我们日常体感最关键的数字服务器卡顿十有八九是随机 IOPS 撑不住。第三个维度是延迟单位是毫秒或微秒。它衡量的是单个 IO 请求从发出到完成的时间又细分为平均延迟和分位延迟比如 p99意思是 99% 的请求都在这个值以内。数据库慢查询、应用超时直接和延迟挂钩。很多人在 4K 随机写的场景还在用dd测带宽那结果当然没有参考价值。所以第一步不是问“怎么测”而是问“我到底要测哪个维度”。1.2 选对工具比跑满参数更重要明确了测试维度接下来就是工具选型。我的实际经验是Linux 下测试磁盘读写工具听过最多的是三类dd、fio、hdparm再加上一个稍微冷门的bonnie各自的定位完全不同。dd是最基础的文件拷贝工具很多教程拿它测速但它的设计初衷根本就不是性能测试工具用起来有很多隐藏的坑。fio是专门为 IO 负载测试设计的工具功能强大到夸张几乎可以说它是磁盘性能测试的事实标准。hdparm适合快速查看磁盘参数和简单的缓存读取测试能力有限。bonnie偏文件系统层面的测试更旧一些。我的建议是正式对比和验收用fio快速摸底用dd也不是不行但心里要清楚它测的是什么。下面详细拆解每个工具尤其是为什么fio能成为我的主力工具。2. 工具选型解析为什么最终我选择了 fio关于工具这一块我踩过的坑特别多值得单独展开说说。很多人都是因为看了网上随手抄的dd命令误把缓存速度当成了磁盘速度导致后续决策跟着出错。2.1 dd 不是不能用而是容易误读结果先看一条网上最常见的测试命令dd if/dev/zero of./testfile bs1G count1 oflagdirect拆开看if/dev/zero表示数据源是零字节流of./testfile写到当前目录的测试文件bs1G是块大小 1Gcount1只写一块。这条命令测的是顺序写带宽。很多人跑完看到 500MB/s 甚至更高就觉得磁盘好快实际上这个结果要打折扣。关键的坑在oflagdirect和省略它的差别。如果不加oflagdirect数据先写进操作系统的 page cache页缓存dd命令可能瞬间就返回了测出来的是内存写入速度而不是磁盘写入速度。加了oflagdirect绕过缓存直写磁盘结果才接近真实。可即使这样dd依然只能测非常粗浅的“理论最大写带宽”它没法控制队列深度没法模拟随机小 IO也没法给出 p99 延迟。拿它测 NVMe 这种高并发设备结果往往偏低因为dd默认只发单队列深度的请求根本喂不饱现代固态。所以我的用法是dd只用来做快速摸底比如怀疑磁盘挂了、判断是不是有明显的硬件故障跑一条几十秒的测试看个大概就行。真要严谨对比绝对不用dd。2.2 fio 为什么是事实标准我第一次用fio的感觉是这才是正经的性能测试工具。fio能自定义 IO 引擎、块大小、队列深度、读写比例、运行时长、并发进程数输出结果里有带宽、IOPS、延迟的各项统计几乎覆盖了性能测试想要的全部维度。先看一个最典型的fio测顺序读命令后面第四部分我会完整展开fio -filename/data/testfile -direct1 -iodepth32 -rwread \ -ioenginelibaio -bs1m -size2G -numjobs1 \ -runtime60 -group_reporting -nameseq_read这里每个参数都有它的业务含义-direct1绕过 page cache 直写磁盘测真实磁盘能力-iodepth32让每进程同时挂 32 个 IO 请求模拟高并发-rwread表示纯顺序读-bs1m用 1MB 大块适合测吞吐。fio设计上就是按“工作负载”去模拟真实业务它不会像dd那样只发单请求而是可以模拟数据库那种 4KB 小随机 IO、视频流那种 1MB 大顺序 IO。生态上fio也是各大存储厂商做性能宣传时最常用的工具很多云厂商公布的磁盘性能指标都是基于fio跑出来的大家用同一个基准才能横向对比。fio本身的性能消耗不小跑测试时它自身也会吃 CPU但相比它提供的可信度这个代价完全可以接受。2.3 hdparm 和 bonnie 适用边界hdparm适合快速查看磁盘参数比如缓存大小、是否支持高级电源管理它还带一个简单的-t测速功能。但它的测速读的是缓存和磁盘的组合速度对现代 NVMe 的支持也有限我不是很喜欢把它当性能测试工具用。bonnie是上世纪留下来的文件系统测试工具测的是文件创建、删除、字符读写、块读写等更偏向文件系统层综合表现现在用得少。比起fiobonnie对并发和随机负载的模拟能力差了一截。直接说结论不管你测 SSD 还是机械盘测文件系统还是裸设备主力工具就锁死fio就对了。接下来这部分是整篇文章最核心的干货影响磁盘测试结果的机制。3. 读懂底层机制影响磁盘测试结果的几个关键点用错参数测出假数据通常是没搞懂操作系统 IO 路径上的几个关卡。把下面这几个机制弄明白你不仅能看懂fio每个参数在干嘛还能自己判断线上业务的 IO 瓶颈到底在哪。3.1 写缓冲Page Cache是最大的“作假”来源Linux 内核默认会把读过的文件数据、写过但还没落盘的数据放在内存里这部分叫 page cache 或者页缓存。进程写文件时数据通常先进入 page cache然后由内核稍后在后台刷入磁盘。这套机制的好处是写入速度快、减少磁盘磨损、提升读写性能但它的副作用是如果测试工具不主动绕过你测到的速度是“内存写速度”而不是“磁盘写速度”。生活化类比你在一家便利店买东西结账收银员先收钱但核账和存钱是后面才做的事。收银员收钱的速度快但真正把钱存进保险柜的速度不一定快。dd没加direct时测的是收银员收钱的速度加了direct才是保险柜存钱的速度。在fio里对应两个重要参数-direct1表示绕过 page cache让 IO 直接下发到块设备层-fsync1表示每个写请求完成后强制调用 fsync把数据真正刷到磁盘上。如果这两个都没设置fio测顺序写的带宽可以高出真实值几倍因为数据全在内存里打转。另一个和缓存相关的变量是测试文件大小。如果测试文件远远小于机器内存即使加了direct1也会遇到这样一种情况测试文件的一部分其实还留在内存的 cache 里读的时候直接被 cache 命中导致读带宽异常高。一般经验是测试文件大小至少要是物理内存的两倍确保冷缓存场景下走真正的磁盘读。3.2 DirectIO、fsync/fdatasync 和队列深度的微妙关系绕过缓存以后IO 就进入块设备层这时候又有一个概念叫队列深度Queue Depth对应fio参数里的-iodepth。队列深度可以理解为同一时间有多少个 IO 请求在路上排队它直接影响设备能不能把并发能力跑满。生活化类比一个餐厅出菜口后厨做好菜放到窗口服务员来取。如果服务员一次只取一道菜队列深度为 1后厨再快也出不了太多菜如果服务员一次取几十道菜队列深度 32后厨满负荷运转吞吐自然上去了。NVMe 固态内部有非常深的硬件队列能同时处理数百个 IO 请求所以iodepth对 NVMe 的影响格外明显对单盘机械硬盘则没那么大因为机械硬盘本质上是单磁头物理上就不支持高度并发。再说fsync刷盘。有些业务比如数据库的事务日志要求每次写入都必须确认落盘这时候测的顺序写性能会远低于普通写入性能因为每次写都要等物理写入完成。fio里可以用下面这两个参数模拟-fsync1每个 IO 完成后刷一次盘模拟最严苛的持久化要求。-fdatasync1只刷数据不刷元数据比 fsync 略轻一点。我的经验是测传统关系型数据库落盘能力时一定要加上-fsync1或者-fdatasync1否则跑出来的数字和真实业务差距能到好几倍。很多人说“我机器顺序写有 1 个 G”结果数据库一跑就卡往往是没意识到数据库日志是频繁 fsync 的两者的模型完全不同。还有ioengine参数。libaio是 Linux 原生的异步 IO 引擎请求提交后不阻塞进程等待适合高并发场景sync引擎则是同步提交一次就发一个请求更像是传统程序的写入方式。跑高并发 IOPS 测试时libaio是唯一能测出设备真实并发上限的方式。3.3 块大小和读写模式怎么配合块大小-bs直接决定一次 IO 携带的数据量它跟测试目标强相关。测顺序大吞吐用大块512KB、1MB因为视频流、备份恢复这类业务本来就是把大块数据连续搬移。测数据库随机读写用小块4K、8K因为数据库页大小通常是 8KB 或 16KB而索引和日志等小 IO 往往只有几 KB。读写模式-rw覆盖四类基础负载模式含义对应业务场景read纯顺序读视频点播、文件下载write纯顺序写日志收集、备份写入randwrite纯随机写数据库事务日志、缓存落盘randread纯随机读数据库查询、虚拟机磁盘读取rw 和 randrw混合读写综合业务可指定读写的比例实际测磁盘时我通常会把这几种模式都跑一遍因为单纯跑一个顺序写是不能反映综合能力的可很多人偏偏只跑一条dd写命令就当测试完了这是我特别想纠正的做法。4. 实操用 fio 跑一套完整的读写性能测试理论讲清楚了真正动手一点都不难。我在下面给出一套可以直接“抄作业”的完整流程包含环境确认、命令参数解释和输出解读每一步都标注了背后的原因。4.1 环境准备先确认系统状态再动手跑测试之前先确认几件事。第一用free -h看物理内存总量测试文件大小要往内存两倍以上设比如物理内存 16G测试文件至少 32G。第二用df -h确认你要测的磁盘挂载点在哪剩余空间是否充足。第三如果机器负载本身已经很高比如 CPU 跑满、线上业务正在高峰期最好先换个时间否则测试结果会跟业务 IO 混在一起还容易影响线上稳定性。测试目录的选择也有讲究。如果你要测整块数据盘的性能就在这个盘的挂载点下建一个专用测试目录比如/data/iotest不要在系统盘/根目录下测。系统盘通常还承担着操作系统读写它的 IO 路径、分区策略和数据盘不完全一样测出来的数字不能代表数据盘。生产环境建议在单独的分区或独立目录里测试测试文件用完直接删除不要污染业务数据区。需要特别提醒的一点fio测试会真打真枪地产生大量写 IO对磁盘健康有轻微影响。SSD 有写入寿命限制这倒不是大问题跑几十秒测试的写入量远小于日常使用但如果拿一块重要数据盘反复跑高强度随机写还是会有不必要的磨损。测试尽量挑独立测试盘或者测完就删文件不要把测试文件留在正式业务分区里过夜。4.2 顺序读写测试验证最大带宽先测顺序读。完整命令长这样fio -filename/data/iotest/testfile \ -direct1 \ -iodepth32 \ -thread \ -rwread \ -ioenginelibaio \ -bs1m \ -size4G \ -numjobs1 \ -runtime60 \ -group_reporting \ -nameseq_read逐条解释我的选型依据-filename/data/iotest/testfile指定测试文件路径fio会在这个路径上创建文件并做 IO。-direct1绕过 page cache直通块设备这保证了测的是磁盘真实能力而不是内存速度。-iodepth32队列深度 32对现代 SSD 来说是个比较合理的起点。NVMe 可以试 64 甚至 128机械盘用 8 到 16 就差不多了。-thread使用线程模式代替进程模式减少上下文切换开销数字更干净。-rwread纯顺序读。-ioenginelibaio异步 IO 引擎这是fio在高并发下最常用的引擎。-bs1m块大小 1MB。为什么不用小块的 4K 来测带宽因为小块 IO 的瓶颈在寻址和 IOPS 上顺序带宽测不出来用大块才能把设备带的满带宽逼出来。-size4G测试文件总大小 4G。先看内存大小调整最好大于内存两倍免得测试过程被 page cache 干扰。-numjobs1单进程。多进程会叠加并发适合模拟高并发场景但单进程跑更能反映单盘基础性能。-runtime60运行 60 秒。时间太短结果波动很大机械盘还要考虑寻道和缓存冷热60 秒起步比较稳妥。-group_reporting当有多个 job 跑的时候汇总显示所有 job 的总和。-nameseq_read任务名称输出结果里能看到方便区分日志。顺序写把-rwread改成-rwwrite就行其他参数不用动fio -filename/data/iotest/testfile \ -direct1 -iodepth32 -thread \ -rwwrite -ioenginelibaio -bs1m -size4G \ -numjobs1 -runtime60 -group_reporting -nameseq_write实测下来一台普通的 NVMe 固态顺序读能跑到 2500MB/s 以上顺序写一般也有 1500MB/s 到 2000MB/s。SATA 固态顺序读约 500MB/s机械盘顺序读写大约在 150MB/s 到 200MB/s。如果明显低于这个区间基本可以怀疑线缆/接口速率问题、固件策略问题或者你的磁盘本身老化了。4.3 随机读写与混合负载模拟真实业务随机 IO 是数据库和虚拟化场景最关键的性能指标。最典型的是 4K 随机读和 4K 随机写。4K 随机读命令fio -filename/data/iotest/testfile \ -direct1 -iodepth32 -thread \ -rwrandread -ioenginelibaio -bs4k \ -size4G -numjobs1 -runtime60 \ -group_reporting -namerand_read_4k-bs4k是关键为什么选 4K因为它最贴近数据库页和文件系统小 IO 的特征。如果换成-bs64k测出来的 IOPS 会低很多但这不代表设备变差了只是“块变小了、IO 次数变多了”的自然结果而已。所以横向对比不同磁盘时必须保证块大小、队列深度、并发数完全一致否则对比毫无意义。4K 随机写类似把-rwrandread换成-rwrandwritefio -filename/data/iotest/testfile \ -direct1 -iodepth32 -thread \ -rwrandwrite -ioenginelibaio -bs4k \ -size4G -numjobs1 -runtime60 \ -group_reporting -namerand_write_4k混合读写用-rwrandrw并可以用-rwmixread70控制读写比例含义是 70% 读、30% 写。这在模拟文件服务器、邮件服务器这类读多写少的业务时很有用。fio -filename/data/iotest/testfile \ -direct1 -iodepth32 -thread \ -rwrandrw -rwmixread70 -ioenginelibaio -bs4k \ -size4G -numjobs1 -runtime60 \ -group_reporting -namemix70_30还有个细节iodepth对随机读写的 IOPS 影响特别大。同一块 NVMeiodepth1时随机读 IOPS 可能只有几千iodepth32时能到几万甚至几十万。设备本身没变变的是你能不能同时把请求喂给它。所以我跑随机 IOPS 测试时习惯把iodepth从 1 到 64 各测一遍记录一条曲线这比单点数据更有说服力。4.4 如何看懂 fio 输出结果fio跑完会输出一大段结果新手看到容易晕但其实关键信息就那么几行。我拿一次随机读的典型输出举例重点看这几个字段read: IOPS152k, BW593MiB/s (622MB/s)(35.6GiB/61439msec) slat (usec): min2, max578, avg14.04, stdev10.39 clat (usec): min54, max2168, avg195.32, stdev85.14 lat (usec): min73, max2372, avg209.48, stdev88.23 clat percentiles (usec): | 1.00th[ 123], 5.00th[ 137], 10.00th[ 145], | 50.00th[ 177], 95.00th[ 300], 99.00th[ 482], | 99.50th[ 578], 99.90th[ 793], 99.99th[ 1044]第一行先说总成绩IOPS152k 意思是 15.2 万次随机读/秒BW593MiB/s 是带宽。第二行slat是提交延迟指 IO 从 fio 进程提交到内核的时间在 libaio 引擎下通常极小。第三行clat是完成延迟指 IO 从提交到完成的时间这才是我们最关心的设备延迟。第四行lat是总延迟约等于 slat clat。最后那个百分位表更关键50.00th 是中位数延迟99.00th 是 p9999.90th 是 p999这决定了极端情况下你的服务会不会超时。我判断一块盘能不能支撑数据库业务不会只看平均值而是重点看 99 分位和 99.9 分位。比如平均延迟 200 微秒不错但如果 p99 到了 2 毫秒对某些要求苛刻的实时服务来说已经偏高了。另一个容易忽略的点fio结果里有时会出现msec单位不是usec看单位要仔细搞混了数字就差了一千倍。5. 实测翻车记录常见问题与排查技巧跑磁盘测试看起来是个流水线操作但实际上翻车的概率不小。我把这么多年踩过的坑汇总成一份排查清单每条都是真实遇到过的。5.1 缓存和机型负载造成的假数据问题一测试文件太小全部命中 page cache。表现是每次跑都破纪录速度高得离谱换大文件立刻现原形。解决方式前面提过测试文件至少为物理内存两倍。如果内存太大没法按两倍设比如服务器内存 512G谁也不会为了测试去建 1TB 文件一个折中办法是把测试文件设成 64G 或 128G同时把-direct1打开效果也基本可信。问题二测试机器不干净后台有其他业务 IO 在跑。表现是顺序读跑出了锯齿一样的曲线一会儿快一会儿慢。我用过最笨但有效的办法测试前用iotop和iostat确认当前磁盘的 IO 活动有条件的直接在业务低峰期跑测试期间不要碰这台机器。问题三把dd未加direct的缓存写入当成真实速度。我已经强调多次这里再记录一个我亲眼见过的案例有人拿dd测一块 SATA 固态写速度 1.2GB/s兴高采烈地以为自己买到了高级盘实际上他测的是跨 NUMA 的内存写带宽。只要加上oflagdirect速度立刻掉回 450MB/s 左右这才是实打实的磁盘写速度。5.2 磁盘状态的隐藏干扰测试 SSD 时有一个机械盘没有的现象测试结果会随着测试时间变长逐渐下降。原因主要两个SSD 的垃圾回收策略、过热降速。现代 SSD 内部有缓存和映射表测试刚开始时数据还能写到 SLC 模拟缓存里跑一段时间缓存耗尽速度就掉到 TLC 真实写入速度。另外高性能 NVMe 长时间高负载会发热主控一旦检测到高温就自动降速。所以测 SSD 的顺序写我建议runtime至少设 120 秒观察前 60 秒和后 60 秒的曲线有没有明显跳水。如果前段 2500MB/s、后段 700MB/s那不是这批盘不行而是它的持续写入性能和缓存策略决定的你要根据业务来判断这算不算缺陷。对于机械盘我踩过另一个坑测试时间太短只跑 10 秒。机械盘的外圈寻道时间比内圈快如果测试数据恰好落在外圈前 10 秒数字会漂亮得误导人一拉长就露馅了。测机械盘至少跑 60 秒以上有条件的最好跑 300 秒把全盘范围都覆盖到。与此同时机械盘的随机读写别指望iodepth32能吃满它的物理结构决定了并发上去以后反而会因为寻道冲突拖慢速度用iodepth8或16比较贴近真实。5.3 环境差异导致的不可对比这是最容易被忽略的一类问题同一个型号的磁盘在不一样的机器和系统上跑出截然不同的数字然后有人就下结论说某某机器有问题其实未必。我自己就遇到过同一批盘一台机器用 ext4 文件系统、默认挂载参数另一台用了 xfs 且开了nobarrier结果随机写 IOPS 差了将近 20%。这个差距不代表盘有优劣而是文件系统日志策略和挂载参数带歪了。所以要横向对比硬件时必须统一软件环境同样的内核版本、同样的文件系统、同样的挂载参数、同样的测试工具版本。我在记录测试结果时会顺手把uname -a、mount | grep /data、fio --version都存到一个结果文件里这样后续复盘才有据可查。另外云服务器的磁盘测试有个特殊之处很多云盘的性能表现跟“突发额度”有关也就是短时间冲高后就会被限流。本地fio能跑出很好看的数据但长时间跑你会发现带宽悄悄降到某个固定数值。这时候你应该结合云平台的说明文档去理解而不是骂硬件。测试云盘时加长运行时间是必要的比如-runtime300看持续性能而不是峰值性能。5.4 IOPS 和带宽的“欺骗性”测出高 IOPS 或者高带宽后还要冷静判断。有一种常见的自欺欺人追求一个漂亮的峰值指标却忘了业务需要的是低延迟。比如在队列深度 256 下4K 随机读 IOPS 能做到 50 万但同时刻延迟已经飙到几十毫秒。数据库业务根本不会在这个深度的极端负载下运行这个 50 万 IOPS 对你有意义吗没有。正确的做法是按业务的真实并发去测。数据库核心业务一般不会用iodepth256这么深的队列更多是 8 到 32视频点播就用大块顺序读根本不关心 4K IOPS。先定负载模型再定参数最后才看数字。否则你就在和一个根本不存在的场景较劲。6. 一点个人经验测试结果记录的姿势测磁盘不是跑完看一眼就结束的事尤其是选型对比和性能调优时一次测试的数据如果没记录好等于白测。我习惯给每个测试结果建一个固定格式的记录包含这些要素测试时间、测试机型号、磁盘型号和固件版本内核版本uname -r文件系统及挂载参数mount输出测试工具版本fio --version本次测试的所有fio参数输出结果的原始文本有了这些记录无论后面是要向团队汇报、向供应商追责还是对比多块盘选型都能拿得出证据而不是“我记得大概是这个数”。最后分享一个实用小技巧fio的测试文件可以自己指定路径测完以后用一条rm删除就行。我习惯把测试文件放在一个专门的目录下比如/data/iotest/而不是直接放在根目录或者其他业务目录。这样测试文件有固定归宿删除时不会误伤其他数据也不会在测试完成后留下一个十几 G 的垃圾文件占空间。尤其在生产服务器上这是我对所有线上操作的基本要求可回退、可清理、不污染。