2. 硬件原理:四种存储介质的读写特性与瓶颈识别
做性能优化,不懂硬件原理,就像修车不看发动机。我这些年碰到过太多人,数据库慢了就加索引,写入慢了就加缓存,结果问题压根不在软件层。说白了,你得先搞清楚数据到底是怎么从硬件里拿出来的。
这一章我们不谈概念,就聊实实在在的读写特性,以及我怎么在现场一眼判断瓶颈在哪。
2.1 机械磁盘(HDD)—— 旋转的瓶颈
机械盘其实很简单,一个盘片在转,磁头左右移动找数据。但你想想看,这中间有两个物理动作要花时间:寻道时间(磁头移到目标磁道)和旋转延迟(等待目标扇区转到磁头下)。我做过测试,一块7200转的SAS盘,随机读IOPS大概只有150~200。为什么?一个随机IO要来回移动磁头,大部分时间都耗在“找”上。
识别机械盘瓶颈的方法,我最常用的是看 iostat -x 1。重点关注这几个指标:
- r/s、w/s —— 每秒读写次数,如果超过单盘IOPS上限(比如200),那盘就是瓶颈。
- await —— 平均I/O等待时间,机械盘上如果超过10ms,说明磁盘忙不过来或者队列深度不对。
- svctm —— 一般认为接近寻道+旋转时间的理论值,如果svctm远大于理论值(比如5ms 变成了 20ms),可能是磁盘有坏道或负载异常。
2.2 固态硬盘(SSD)—— 没有机械臂但依然有瓶颈
SSD用的是NAND Flash,没有机械移动,随机访问延迟一下子降到零点几毫秒。但是,SSD也有自己的烦心事。我之前遇到过一块企业级SATA SSD,用了两年突然写入性能暴跌到原来的十分之一。查了日志才发现是垃圾回收(GC)在作祟——后台不停地搬数据、擦块,前台写入就被堵住了。
这里要记住一个概念:写入放大因子(WAF)。你写4KB数据,可能实际擦写了一个256KB的块,这就是放大。WAF越高,寿命越短,性能越差。所以识别SSD瓶颈,除了看 iostat,还要看SMART信息里的Media_Wearout_Indicator和Average_Erase_Count。
fio 做压力测试,看看不同队列深度下4K随机写的表现。如果深度增加但IOPS不涨,很可能就是盘内部在忙GC。还有个常见误区:有人觉得SSD不用做分区对齐。我曾经在旧机器上装了一块SSD,没对齐分区,结果是每次读写都要跨两个Page,性能损失30%以上。嗯,分区对齐这步千万别省。
2.3 内存(DRAM)—— 快到极致但易失
内存的访问延迟大概在几十纳秒,比SSD快三个数量级。但内存的瓶颈往往不在速度,而在容量和带宽。我碰到过一个案例:一台32GB内存的服务器,跑着多个Java微服务,Swap不断被触发。你以为数据在内存里,其实已经被换到盘上了,性能自然一落千丈。
识别内存瓶颈,我习惯看这几个地方:
- /proc/meminfo 的 SwapTotal 和 SwapFree —— 如果Swap用了很多,说明物理内存不够。
- free -h 的 available —— 这个值比buffers/cache更靠谱。
- vmstat 1 的 si/so —— 如果swap in/out持续大于0,内存就是瓶颈。
- NUMA 节点间的访问延迟 —— 在多路服务器上,跨Node访问内存延迟会明显增加。我曾在数据库压测时发现某条SQL响应时间忽高忽低,查了numastat才发现内存分配没绑CPU核心。
2.4 NVMe —— 解放PCIe的潜力
NVMe本质上是把SSD接到PCIe总线上,用AHCI/SATA协议那套束缚全扔了。老式SATA SSD的队列深度只有32,NVMe可以到64K。而且NVMe驱动层极简,延迟可以低到几个微秒。我记得第一次在实验室测Intel P4610,4K随机读到100万IOPS,当时真的震撼——十年前一台服务器可能还跑不到这个数。
但是NVMe也有自己的瓶颈。很多人以为换上NVMe就万事大吉,其实不然。我遇到过几个场景:
- CPU瓶颈:NVMe的IOPS太高,中断处理把CPU吃满了。这时候要用
irqbalance把中断分发到不同核心,或者用nvme-cli查看MSI-X队列。 - PCIe带宽:如果你插的NVMe盘是x4链路,一块盘占满PCIe 3.0 x4的带宽(约4GB/s)很容易。一个x16槽插四块盘,读写一起上,PCIe带宽可能先撑不住。
- 驱动和固件:早期一些NVMe盘的驱动有bug,导致队列深度大了之后莫名卡顿。我建议投产前先用
fio --ioengine=io_uring跑个24小时压测。
2.5 综合对比与瓶颈识别方法论
下面这张表是我常用来跟团队讲差异的,也方便你在现场快速对照。
| 特性 | HDD (15K SAS) | SATA SSD | NVMe SSD | DRAM |
|---|---|---|---|---|
| 顺序读延迟 | 4~8 ms | 0.1~0.3 ms | 0.01~0.05 ms | 50~100 ns |
| 随机读IOPS (4KB) | 200~300 | 30k~80k | 500k~1M+ | ∞ (带宽受限) |
| 顺序写带宽 | 200~300 MB/s | 500~600 MB/s | 3~7 GB/s | 50~150 GB/s |
| 最大容量 | 20 TB+ | 8 TB | 15 TB | 2 TB (单机) |
| 写入耐受度 | 极高 (无寿命问题) | 中等 (DWPD 1~3) | 高 (DWPD 3~10) | 非持久化 |
| 队列深度 | 1 (原生) | 32 (AHCI) | 64k (NVMe) | N/A |
这张图是我自己总结的“存储层级瓶颈识别路径”,从应用层往下看:
实际工作中我一般这么排查:
- 先看应用层的io_pattern —— 是顺序还是随机?读多还是写多?用
strace -p或者perf top抓一下系统调用。 - 再看操作系统层 ——
free -h看available,如果很大说明Page Cache够用;iostat -x 1看 %util 和 await。 - 然后深入设备层 —— 用
smartctl -a /dev/nvme0看温度、健康度、写入量;用nvme list查看队列数。 - 最后用fio做基准测试 —— 确认硬件实际能达到的性能,和预期对比。比如标称随机读500k IOPS,你测出来只有200k,那肯定有问题。
iostat 和 smartctl 开始,花五分钟看看硬件在干嘛。