2. 硬件原理:四种存储介质的读写特性与瓶颈识别

做性能优化,不懂硬件原理,就像修车不看发动机。我这些年碰到过太多人,数据库慢了就加索引,写入慢了就加缓存,结果问题压根不在软件层。说白了,你得先搞清楚数据到底是怎么从硬件里拿出来的。

这一章我们不谈概念,就聊实实在在的读写特性,以及我怎么在现场一眼判断瓶颈在哪。

2.1 机械磁盘(HDD)—— 旋转的瓶颈

机械盘其实很简单,一个盘片在转,磁头左右移动找数据。但你想想看,这中间有两个物理动作要花时间:寻道时间(磁头移到目标磁道)和旋转延迟(等待目标扇区转到磁头下)。我做过测试,一块7200转的SAS盘,随机读IOPS大概只有150~200。为什么?一个随机IO要来回移动磁头,大部分时间都耗在“找”上。

我的经验:有一次线上MySQL的binlog写入一直抖,iowait长期飙到30%+。我查了下磁盘类型,确认是15K SAS盘。后来换了一台带NVMe的机器,写入延迟直接从5ms降到0.1ms。有时候问题就这么简单——你给机械盘塞了随机写入的任务。

识别机械盘瓶颈的方法,我最常用的是看 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。

关键指标对比:企业级SSD随机读IOPS可以达到几十万,但是一旦队列深度不够或者有GC在跑,IOPS可能掉到几千。我常用的工具是 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核心。
注意:别把内存带宽和延迟搞混。单条内存带宽可能高达几十GB/s,但如果你的应用是随机小数据访问(比如Redis的hash),延迟才是关键。带宽瓶颈一般只在顺序大块读写(如视频流、大型文件拷贝)时才会凸显。

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

这张图是我自己总结的“存储层级瓶颈识别路径”,从应用层往下看:

存储层级与瓶颈识别路径 存储层级与瓶颈识别路径 应用程序 I/O 系统缓存 (Page Cache) NVMe驱动 SATA/AHCI 内存控制器 NAND Flash (SSD) 磁盘盘片 (HDD) DRAM芯片 瓶颈识别路径 请求路径逐层排查:应用层→缓存命中率→协议队列深度→物理介质的IOPS/延迟 常用工具:iostat、perf、fio、nvme-cli、SMART

实际工作中我一般这么排查:

  1. 先看应用层的io_pattern —— 是顺序还是随机?读多还是写多?用 strace -p 或者 perf top 抓一下系统调用。
  2. 再看操作系统层 —— free -h 看available,如果很大说明Page Cache够用;iostat -x 1 看 %util 和 await。
  3. 然后深入设备层 —— 用 smartctl -a /dev/nvme0 看温度、健康度、写入量;用 nvme list 查看队列数。
  4. 最后用fio做基准测试 —— 确认硬件实际能达到的性能,和预期对比。比如标称随机读500k IOPS,你测出来只有200k,那肯定有问题。
总结一句话:硬件瓶颈识别,很多时候不是知识问题,而是习惯问题。我见过太多人第一反应去调SQL或改代码,结果花了一周发现是新换的SSD固件版本有bug。下次遇到性能问题,不妨先从 iostat 和 smartctl 开始,花五分钟看看硬件在干嘛。