前几天凌晨两点接到电话,客户那边数据库日志刷屏,应用全部卡死。远程上去第一件事不是查SQL,而是先看一眼磁盘到底什么状态,因为经验告诉我,很多慢得像蜗牛的系统,根子都在存储I/O上。这时候我最先摸出来的工具,永远都是dd和hdparm这两个老伙计。它们不是性能测试软件里最专业的,却一定是排查现场最管用的。dd负责构造真实读写压力,hdparm负责快速读取硬盘硬件能力,一写一读配合起来,五分钟之内就能判断磁盘有没有“带病上岗”,给后面的优化争取时间。
这篇分享适合所有Linux运维、DBA、存储工程师,以及那些刚接触性能排查的初学者。我会把命令逐条拆开讲清楚,包括为什么要加某个参数、数字怎么换算、结果怎么解读,还会把我在现场踩过的几个坑一并说出来,这些坑没有一次是白踩的,希望你能直接绕过去。
1. 为什么第一时间想到 dd 和 hdparm
1.1 应急检测的“老两样”
一提到磁盘性能测试,很多人第一反应是装fio,配置脚本、跑随机读写、出IOPS报告,很专业,可问题是你得先有fio,而且配置格式还要花五分钟回忆。生产故障现场最缺的就是时间,最不缺的是恐慌。dd几乎出现在每一台Linux服务器上,hdparm稍微冷门一点,但大部分发行版也自带,即使没有,一条yum install -y hdparm或者apt-get install -y hdparm就能解决,不依赖复杂的编译环境,不牵扯额外的内核模块,干净利落。
这两个工具还有一个共同特点:它们测试的是顺序大块I/O能力。很多业务卡顿确实先表现为顺序读写的瓶颈,比如数据库全表扫描、日志批量写入、备份文件传输,这些场景下顺序吞吐量就是命门。所以用来做第一轮健康度筛查,它们完全可以胜任。等到确认磁盘“可能有问题”,再上fio这类重武器做精细化的随机I/O和延迟分析,这样的排查路径效率最高。
1.2 两个工具的分工完全不一样
这是我见过最多人混淆的地方。hdparm主要面向块设备本身,它可以查询硬盘参数、启停写缓存、设置高级电源管理,性能测试只是它的附带功能,-t参数直接从磁盘读数据,绕过了文件系统这一层;dd则是一个数据拷贝工具,它既可以写文件到文件系统,也可以读写裸设备,测试范围更灵活。
两者配合的逻辑非常清晰:hdparm用来测“盘本身”的极限读取速度,dd用来测“当前实际环境”下能跑出什么样的写速度和读速度。换句话说,hdparm回答“这块盘大概能跑多快”,dd回答“你这台机器当前配置下能跑多快”。两者有差距很正常,如果差距过大,说明瓶颈不在盘,而在文件系统、阵列卡缓存策略、总线带宽或者内核参数。
1.3 什么场景不适合用它们
先泼一盆冷水。dd和hdparm测的都是顺序流,测不出随机小I/O的延迟分布,也模拟不了数据库那种大量并发、混合读写的实际压力。你拿它们测出一片漂亮的数字,不代表你的数据库撑得住高峰期;反过来,数字很难看,往往意味着盘真的有问题,即使随机性能平时表现还行,顺序都上不去的盘大概率也该换了。
所以我把这两个工具定位成“急诊听诊器”,而不是“影像科MRI”。要做存储选型、容量规划、压测调优,还是老老实实上fio,用runtime=300、rw=randrw、iodepth=32这样的补充测试,再加上iostat和blktrace去深挖延迟指标。这篇分享的主旨正是告诉你,怎么把听诊器用到极致。
2. 测试前必须搞清的底层原理
2.1 一条 dd 命令里的参数拆解
dd的语法看起来简单,实际上每个关键参数都踩过雷。最常见的写测试命令长这样:
dd if=/dev/zero of=/mnt/data/disk_test.img bs=1M count=2048 conv=fdatasync拆开看:if指定输入文件,这里用/dev/zero生成无限零字节,不会受源盘读取速度干扰;of指定输出文件,一般放到你要测试的目标挂载点下面;bs=1M表示每次读写1MiB的数据块,这是顺序测试常用的块大小,太小了测出来的不是盘的速度而是系统调用的开销;count=2048表示传输2GiB数据,至于为什么是这个量级,后面细说;最容易被忽略的是conv=fdatasync,它强制在命令结束前把文件数据落盘,确保数字反映的是真实写入性能而不是页缓存里的“假写”。
很多人会问,bs=1M count=2048最终写入多大?早期我用过一个土办法,bs=1M count=2048传入strace看实际字节数,其实不用这么麻烦,直接用dd --help查看说明,或者自己在心里算:2048块乘以1MiB等于2048MiB,约2.15GB。这个数量级是有讲究的,如果只写100MB,结果会被磁盘自带缓存和文件系统缓存完全吃掉,测出来的数字能飙到几千MB/s,完全失真。
还有一个参数也值得写进测试命令:oflag=direct,它让dd绕过页缓存直接写设备,和conv=fdatasync目的类似但实现方式不同。两者的选择看场景:oflag=direct更接近物理设备真实速度,但要求bs和底层扇区对齐,有些老文件系统不兼容;conv=fdatasync更通用,先写到缓存再同步落盘,兼顾真实性和兼容性,适合绝大多数现场。我个人的习惯是第一遍用conv=fdatasync,如果怀疑结果虚高,再加oflag=direct复测一次。
2.2 hdparm 的 -t 和 -T 为什么结果差这么多
hdparm的读性能测试有两个经典参数,一个是-t,一个是-T。写过几千次命令之后,我发现大多数人从没认真看手册里的解释,导致数字出来以后一头雾水。
hdparm -t /dev/sda hdparm -T /dev/sda-t测的是从磁盘实际读取数据的速率,这个过程中数据会经过Linux页缓存,但在读取时会尽量利用设备自身的扇区缓存;-T测的是从内存缓存中读取数据的速率,它反映的主要是CPU、内存、总线和缓冲区管理的性能,跟磁盘本身的关系不大。所以你会看到-T的数字通常比-t高出一个量级,服务器内存带宽动辄几万MB/s,而一块机械盘的物理极限也就是两百多MB/s。很多人拿-T的数字去证明磁盘很快,这就是典型的把裤腰当领带,完全用错了地方。
更专业的做法是给-t加上--direct参数,变成hdparm -t --direct /dev/sda。这一下就绕过了文件系统页缓存,让磁盘控制器直接和设备通信,数字更接近硬件真实能力。我第一次在SSD上跑这个命令时,数字比不带--direct低了不少,一度以为是盘坏了,后来想明白,之前的“高速”其实是在消费已经预读进内存的数据。所以记住:要测硬盘物理读性能,-t --direct才是正经姿势。
2.3 直写模式与页缓存:为什么测出来“假快”
Linux内核会尽量用空闲内存做文件缓存,你往磁盘写数据时,数据首先进入内存页缓存,内核再异步回写到磁盘。这个过程本身没有问题,问题出在测试工具上。如果你执行dd if=/dev/zero of=/mnt/data/test.img bs=1M count=2048而不加conv=fdatasync或者oflag=direct,dd把数据交给页缓存就返回了,整个耗时可能只有几百毫秒,计算出来的速率是内存速率,而不是磁盘速率。
读测试同样有这个问题。第一次读取某个文件时,数据会从磁盘进入页缓存;第二次再读同一个文件,几乎全部命中缓存,速度翻几倍毫不奇怪。曾经有个同事拿着测试数据跟我说盘有800MB/s的读能力,那是块SATA SSD,我让他先echo 3 > /proc/sys/vm/drop_caches清理页缓存再测一遍,数字立刻掉到500MB/s左右。这个操作在生产库上最好不要乱用,因为会牺牲所有缓存热数据,但测试环境下想得到真实结果,就必须面对缓存这个“作弊器”。
另一种思路是直接测字符设备或块设备。裸设备的读取不走文件系统页缓存,结果更干净,但风险也更大,一个参数写错,可能把盘上的数据抹掉。所以我在生产机上更推荐用文件系统加conv=fdatasync的组合,既安全又能反映真实使用场景。
3. 实操过程与完整命令序列
3.1 测试前的环境确认
动手之前先花一分钟看环境,这一步能避免后面出现灾难性错误。我通常执行三条命令:
lsblk df -h cat /proc/meminfo | grep MemTotallsblk确认盘符对应的设备名,特别注意那种机器上有好几块盘的情况,千万别把系统盘当成测试对象;df -h确认挂载点剩余空间,至少要有测试文件的三倍余量,防止磁盘写满引发文件系统异常;看内存则是为了估算页缓存的影响范围,内存越大,缓存对短时长测试的干扰越明显。
如果测试对象是已经承载业务的服务器,还需要确认当前磁盘I/O压力。可以直接快速看一眼iostat -x 1 3,如果%util已经接近100%,那你测出来的数字是“挤出来”的,不是真实水平,最好先和业务方确认窗口期再做。我见过有人不管三七二十一直接开测,结果数据难看得很,查了半天才发现是另一个定时任务正在跑全量备份,白白浪费时间。
3.2 hdparm 读测试实战
找一块没有业务写入的盘,如果你只想做健康检查,在系统盘上做只读测试是安全的,因为这个测试不修改数据。命令如下:
hdparm -t --direct /dev/sda一块SATA机械盘上常见输出是:
/dev/sda: Timing buffered disk reads (O_DIRECT) = 486 MB/sec这个486MB/s对于机械盘来说明显偏高,很可能是设备有较大缓存或者磁盘本身是混合盘。如果数字跑到1200MB/s以上,基本可以判定是SSD或者带缓存阵列。要注意的是,hdparm在部分云主机和虚拟化环境下会显得无效,因为它直接发ATA命令给磁盘,而虚拟机磁盘控制器往往不响应这类命令,报错“HDIO_DRIVE_CMD(identify) failed”时不要慌,这是环境特性,不代表磁盘坏了,换dd继续测就行。
为了降低偶然性,我习惯连续测三次取中位数:
for i in 1 2 3; do hdparm -t --direct /dev/sda | grep "Timing"; done数字波动在10%以内都算正常,如果三次结果忽高忽低,差值超过30%,就要怀疑磁盘有坏道、固件在做后台清理或者链路不稳定。
3.3 dd 写测试实战
接下来写测试,这是整个流程里风险最高的环节。我的铁律是:写测试永远只写在文件系统挂载点下新建的临时文件上,绝不直接写裸设备。测试完立即删除临时文件。
dd if=/dev/zero of=/mnt/data/.disk_test_$$.img bs=1M count=2048 conv=fdatasync && rm -f /mnt/data/.disk_test_$$.img加$$是为了让文件名带上当前进程号,避免并发测试时多个进程写到同一个文件。count=2048意味着写入2GiB,对于常见SSD,耗时也就两三秒,对于慢速机械盘可能需要二三十秒,这个时长足够让磁盘脱离缓存进入稳态。如果测试时间太长,可以适当调低count,但最低不要低于512,否则结果没有参考价值。
执行完你会看到类似这样的输出:
2048+0 records in 2048+0 records out 2147483648 bytes (2.1 GB, 2.0 GiB) copied, 6.53412 s, 329 MB/s最后一行的329MB/s就是顺序写吞吐量。这个数字是否正常,得结合磁盘类型判断。如果是SATA SSD,329MB/s偏低,但还算合理;如果是NVMe SSD,那几乎可以断定有问题,常见NVMe写速度都应该在1000MB/s往上。
3.4 dd 读测试实战
读测试有两种做法,一种是读刚才写的文件,一种是直接读裸设备。读文件的好处是安全,不涉及无关数据,但要注意顺序:写测试完成后立刻读,数据可能还在页缓存里,测出来是内存速度,所以要先清缓存。清缓存需要root权限:
echo 3 > /proc/sys/vm/drop_caches然后执行:
dd if=/mnt/data/.disk_test_$$.img of=/dev/null bs=1M count=2048读文件的数字代表“文件系统层看到的速度”,包含了文件系统元数据开销。如果想更纯粹地测盘,可以用:
dd if=/dev/sda of=/dev/null bs=1M count=2048这个命令直接读取磁盘前2GiB数据,不经过文件系统,但会读到磁盘的前部区域,那里通常是分区表和启动引导,不能反映整盘特性。更公平的做法是先parted看分区起始位置,再用dd从指定偏移读取,但这套操作对新手来说过于复杂,而且读取裸设备时的skip参数一旦写错,可能会读到敏感数据区域,所以我只在专业测试时用,快速排查时读临时文件就够了。
3.5 结果换算与多次采样
现场还有一种情况,就是你知道测出来的数字对不对,但别的同事问起“你这个顺序写多少IOPS”,你得知道怎么换算。顺序I/O场景下的公式非常简单:
IOPS = 吞吐量(MB/s) * 1024 / 块大小(KB)如果测出500MB/s,块大小是4KB,那么IOPS就是500*1024/4=128000。但这里有个陷阱,顺序测试的块是1MB,换算成4KB的随机IOPS毫无意义,因为顺序和随机的物理开销完全不同。所以正确的做法是:就着你的数据类型来说话,测试文件系统迁移时用大块顺序指标,测试数据库日志盘时用小块同步写指标。
多次采样方面,我一般会跑三轮,取中间那次数,同时注意每次测试之间间隔至少几秒钟,让磁盘控制器内部队列清空。如果三次数值差距大,我会再看磁盘的SMART信息:
smartctl -a /dev/sda重点关注Reallocated_Sector_Ct和Current_Pending_Sector,这两个指标异常往往说明盘内部已经有物理坏块,测试数字不稳定是正常的,备件可以提前申请了。
4. 结果解读与快速定位
4.1 典型数值分档参考表
为了让快速判断更有依据,我把自己在常见设备上的实测经验整理成一张参考表。这些数字不是规格书上的峰值,而是真实环境下带文件系统开销的典型值,不同固件和驱动会有出入,但作为初步定位足够了:
| 设备类型 | 顺序读(MB/s) | 顺序写(MB/s) | 备注 |
|---|---|---|---|
| 机械盘 7200rpm SATA | 120~200 | 100~180 | 读写差别不大,写略低 |
| 机械盘 10000rpm SAS | 180~260 | 150~230 | 企业盘缓存策略影响明显 |
| 入门级SATA SSD | 400~550 | 300~500 | 写速受SLC缓存影响大 |
| 中高端NVMe SSD | 2500~7000 | 1500~5000 | 看PCIe代际和型号 |
| RAID5阵列(4块SATA SSD) | 800~1200 | 500~800 | 写惩罚导致写明显低于读 |
注意,这个表是“有缓存策略加持”的常见结果,不是绝对标准。同一块盘,文件系统是ext4还是xfs,挂载参数有没有noatime,都能影响最终数字。所以更靠谱的方式是拿同型号健康盘在同一环境跑一遍同样的命令,做横向对比,那才是真正可信的基准。
4.2 结果异常时从哪里查起
如果dd测出来的写速度只有几十MB/s,别急着给磁盘判死刑,按照下面的顺序排查,能省掉很多冤枉路。
第一步查链路状态。lspci看磁盘控制器是不是跑在预期速率上,NVMe盘插在PCIe 3.0 x4插槽上理论带宽约3500MB/s,如果降到x1模式速度直接砍到不到900MB/s。第二步查磁盘固件和日志,dmesg | grep -i error看有没有大量I/O错误,一条ata bus error往往预示着线缆松动或盘即将损坏。第三步查阵列卡策略,很多RAID卡默认开启写缓存,掉电后可能丢数据,有些场景下管理员会强制关闭写缓存,导致写性能断崖式下滑,这时候要在阵列卡管理界面确认缓存策略。第四步再回到应用层,看看是不是文件系统碎片太多、磁盘空间剩余不足5%、还是iostat中await飙升而svctm正常,回答清楚这些,磁盘背锅的冤案就少了很多。
5. 实测中踩过的坑
5.1 把测试写在系统盘上,差点挤爆根分区
有一年给客户做迁移评估,临时挂载点不够用,我图省事直接把测试文件放到了/根目录下。/dev/zero写出来的零字节文件是稀疏的,但dd写入的是全量数据,不会自动稀疏化,2GiB文件很快把根分区剩余空间吃了大半,导致系统日志服务开始报磁盘满,各种应用出现间歇性卡顿。后来我养成了习惯,写测试之前先看挂载点剩余空间,并且测试文件名一律带.test后缀,设置crontab每天自动清理超过一天的.test文件。
5.2 写裸设备把分区表擦掉,只能重建
这个坑最痛。当时要在客户服务器上对一块没有挂载的“空盘”做性能测试,我想当然认定它没有数据,直接执行了dd if=/dev/zero of=/dev/sdb bs=1M count=1024。测试倒是顺利完成,随后客户才告诉我说那块盘上保存着旧备份。由于分区表已被清零,数据恢复软件扫了好几天才找回大部分文件。那次之后我立了一条规矩:除非能明确百分之百确认盘上无数据且已经过书面确认,否则禁止用dd写裸设备。用lsblk -f查看文件系统信息后还有疑虑,就宁可把盘拔下来插到测试机上去测,绝对不在生产环境边界模糊的盘上做写测试。
5.3 清理缓存误伤了数据库热数据
有一次为了测真实读速度,我在一台正在运行业务的数据库服务器上执行了echo 3 > /proc/sys/vm/drop_caches,结果缓存清理后数据库缓存命中率骤降,SQL查询延迟明显上升,业务方立刻收到了告警。幸好影响只持续了十几分钟,缓存重新预热后恢复,但那次事件被写进了事故复盘报告,过程极其尴尬。正确的打开方式是:做这样的测试前,先确认该主机没有承载核心在线业务,如果必须测,就利用业务低峰期,并且提前和团队报备。
5.4 单次测试误判性能,没有多次采样
我还遇到过因为单次测试数字不理想,直接投诉供应商硬盘质量的情况。第一次dd写测只跑出250MB/s,供应商换了一块盘,结果还是一样,最后查出来是文件系统挂载参数里barrier=1导致的写入延迟,和盘本身毫无关系。单次测试的偶然因素太多,机械盘外圈和内圈速度能差30%,SSD垃圾回收周期会周期性吃掉一段性能,系统后台任务也会抢资源。所以现在哪怕只是快速测试,我也会至少跑三遍,尽量在同一个时间段完成,避免拿到“假故障”数据。
6. 常见问题与排查速查表
6.1 为什么 hdparm 测出的数字比 dd 高那么多
hdparm测的是设备层读取极限,没有文件系统开销,还可能在读取时命中了磁盘自身的缓存;dd测的是文件系统到应用层的完整链路,包含了写文件、分配元数据、目录更新等开销,二者差异大完全正常。判断磁盘健康时以dd的结果为准更贴近实际业务,hdparm的数字主要用于验证盘的硬件上限。
6.2 为什么同一块盘两次 dd 结果差出两倍
最常见的原因就是页缓存。第一次写数据时数据还留在缓存里,第二次复测时直接复用了缓存结果;读取测试更明显,第二次读完全命中内存。解决方法是清理页缓存,或者在不同文件上重复测试。另一个原因是测试时间点不同,机械盘在不同磁道的位置速度差很多,文件系统碎片也会放大这种差距。
6.3 conv=fdatasync 和 oflag=direct 有什么区别,二选一还是都用
两者都是为了让测试结果真实落盘。fdatasync是命令结束后调用一次fsync,它的开销集中在最后一步,测试过程本身还是在用缓存;oflag=direct是绕过缓存直接写,持续消耗真实I/O资源。二选一推荐fdatasync,兼容性最好;对结果有怀疑时再用oflag=direct验证。两个一起用时要注意文件系统是否支持直接I/O,部分网络文件系统不支持,命令会直接报错。
6.4 能不能在数据库服务器上直接跑 dd 测试
可以,但有条件。选择业务低峰期,尽量在独立的挂载点上测试,避免和数据库文件放在同一文件系统;测试前用iostat确认当前I/O压力不大;清理缓存的操作必须谨慎,或者干脆不清缓存而是采用冷文件方式测试,也就是先写一个文件,等几分钟后再读,这样即使有缓存,数字也不会失真太多。
6.5 什么情况下应该放弃 dd 和 hdparm 改用专业工具
当你要为采购选型出具报告、要评估数据库IOPS能力、要定位延迟毛刺问题时,dd和hdparm给不了你足够细的数据,前者是顺序大块测试,后者只有读测试。这时候直接上fio,用--rw=randrw、--bs=4k、--iodepth=32、--numjobs=8这样的组合模拟真实随机负载,才能获得可以做量化决策的数据。记住,快速测试是为排查服务的,最终结论一定要靠更严谨的基准工具来支撑。
6.6 排查指令速查表
| 目的 | 命令 | 注意事项 |
|---|---|---|
| 查看磁盘与分区信息 | lsblk -f | 先确认测试对象 |
| 查看实时I/O压力 | iostat -x 1 5 | %util接近100%时结果不可信 |
| 测试磁盘读速度 | hdparm -t --direct /dev/sda | 虚拟机环境下可能不支持 |
| 清理页缓存 | echo 3 > /proc/sys/vm/drop_caches | 生产环境谨慎执行 |
| 测试文件写速度 | dd if=/dev/zero of=/path/test bs=1M count=2048 conv=fdatasync | 临时文件务必清理 |
| 测试文件读速度 | dd if=/path/test of=/dev/null bs=1M count=2048 | 测前写文件并等待冷却 |
| 检查磁盘SMART信息 | smartctl -a /dev/sda | 关注坏块计数 |
| 检查内核I/O报错 | dmesg | grep -i error | 连续报错立即停测 |
这套组合拳打下来,磁盘健康状态基本心里有数了。最后再分享一个我自己的习惯:测试结果我会记到运维笔记里,配合当时的iostat截图和文件系统挂载参数一起归档。下次哪个应用再报慢,我会先翻出历史基线数据对比,一眼就能看出磁盘性能是“一如既往”还是“突然劣化”,这个习惯帮我免掉了无数次重复测试。磁盘性能排查不是跑一条命令就完事的事,工具越简单,越考验使用者对系统原理的理解,希望这篇分享能让你下一次遇到存储问题的时候,心里更有底。