1. 存储性能基础:IO栈全景图、IO路径与延迟构成、性能指标
做存储性能优化,第一件事不是调参数,而是搞清楚数据到底走了哪条路。我见过太多人一上来就改内核参数,结果问题根本不在那里。说白了,你得先看懂IO栈,才能知道延迟从哪来。
1.1 IO栈全景图:数据从应用到磁盘的完整旅程
你写一个 write() 系统调用,数据不是直接飞到磁盘上的。它要穿过用户态、内核态、文件系统、块层、驱动,最后才落到硬件。我习惯把这趟旅程分成四层:
- 应用层:你的业务代码,调用
read/write,或者用 libaio、SPDK 这类用户态框架。 - 内核VFS层:虚拟文件系统,负责把系统调用翻译成通用文件操作。这里会做权限检查、路径解析。
- 文件系统层:ext4、xfs、btrfs 这些。负责把文件逻辑地址映射成块逻辑地址,还要处理元数据、日志、缓存。
- 块层与驱动:通用块层做 IO 调度、合并、排队。驱动层最终把命令发给硬件(NVMe、SATA、iSCSI 等)。
嗯,这里要注意:每一层都可能成为瓶颈。我曾经在一个项目中,发现应用层 IO 深度只有 1,结果磁盘利用率不到 10%,延迟却很高。问题不在磁盘,而在上层没把 IO 发起来。
下面这张图,我把它叫做「IO栈全景图」。你盯着它看五分钟,就能理解数据是怎么一步步走下去的。
1.2 IO路径与延迟构成:时间都花在哪了?
一个 IO 请求从发起到完成,延迟可以拆成几段。我个人习惯用 「前端延迟」和 「后端延迟」来划分。
| 延迟阶段 | 典型来源 | 占比(典型场景) |
|---|---|---|
| 应用处理延迟 | 序列化、业务逻辑、内存拷贝 | 5% - 15% |
| 系统调用延迟 | 上下文切换、VFS处理 | 5% - 10% |
| 文件系统延迟 | 元数据操作、日志提交、锁竞争 | 10% - 30% |
| IO调度延迟 | 请求排队、合并等待 | 5% - 20% |
| 硬件延迟 | 寻道、旋转、NAND编程、网络 | 30% - 70% |
你想想看,如果硬件延迟占了 70%,你优化应用层代码,效果能有多大?反过来,如果硬件延迟只有 30%,但文件系统锁竞争占了 40%,那你换再快的盘也没用。
iostat -x 1 看 await 和 svctm(或 aqu-sz)。如果 await 远大于 svctm,说明延迟主要在排队,不在硬件。这时候调调度器或加大队列深度,比换盘管用。
1.3 性能指标:IOPS、带宽、延迟的物理含义与测量方法
这三个指标,是存储性能的「三驾马车」。但很多人只背概念,不理解它们之间的制约关系。
IOPS(每秒IO操作数)
说白了就是「每秒能处理多少个请求」。它跟 IO 大小强相关。4K 随机读,NVMe SSD 能做到百万级 IOPS。但换成 1MB 顺序写,IOPS 可能掉到几千。为什么?因为硬件处理大 IO 的时间更长。
测量方法:用 fio 是最常见的。我一般这样测随机读 IOPS:
fio --name=randread --ioengine=libaio --iodepth=64 \
--rw=randread --bs=4k --direct=1 --numjobs=1 \
--size=10G --runtime=60 --time_based
注意 --direct=1,跳过页缓存,测的是真实硬件性能。如果不加这个,你测出来的可能是内存速度。
带宽(吞吐量)
带宽 = IOPS × IO大小。这个公式很简单,但很多人忽略了一个事实:带宽有上限。PCIe 3.0 x4 的理论带宽约 4GB/s,NVMe 盘再快也突破不了这个物理限制。
我曾经帮一个团队排查问题,他们用 8 块 SSD 做 RAID 0,但总带宽只有 2GB/s。一查,PCIe 插槽是 x2 的,带宽被卡死了。换到 x16 插槽后,带宽直接翻倍。
测量带宽,把 --rw 改成 read 或 write,--bs 设大一点(1M):
fio --name=seqread --ioengine=libaio --iodepth=16 \
--rw=read --bs=1m --direct=1 --numjobs=1 \
--size=10G --runtime=60 --time_based
延迟(时延)
延迟是「一个请求从发出到完成的时间」。它通常用 P99(99% 的请求延迟低于这个值)来衡量,而不是平均值。平均值会掩盖抖动。
举个例子:某系统平均延迟 2ms,但 P99 是 50ms。这意味着 1% 的请求要等 50ms。对于数据库这种场景,这 1% 的慢请求可能就导致超时。
测量延迟,用 fio 的 --latency-percentiles 参数:
fio --name=latencytest --ioengine=libaio --iodepth=1 \
--rw=randread --bs=4k --direct=1 --numjobs=1 \
--size=1G --runtime=30 --time_based \
--latency-percentiles=1
这里 --iodepth=1 是关键。队列深度为 1 时,测的是纯硬件延迟,没有排队干扰。
1.4 三个指标的制约关系
你不可能同时让 IOPS、带宽、延迟都最优。这就像「不可能三角」:
- 追求低延迟 → 队列深度要小 → IOPS 上不去
- 追求高 IOPS → 队列深度要大 → 延迟会升高
- 追求高带宽 → IO 要大 → IOPS 自然下降
我建议你在做性能测试前,先明确目标:是 OLTP 场景(低延迟、高 IOPS),还是 OLAP 场景(高带宽)?目标不同,优化方向完全不同。
好了,这一章的内容就是这些。记住 IO 栈的每一层,理解延迟从哪来,学会用 fio 测三个指标。后面我们会深入每一层,讲具体的优化手段和故障排查方法。