Linux 基础体系 · 第 60/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。
Linux 磁盘性能诊断:iostat、延迟、队列深度、吞吐和饱和度
磁盘性能问题很少能用“磁盘利用率 100%”一句话解释清楚。一次 I/O 从进程发起到设备完成,可能经过页缓存、文件系统、块层、设备映射层、调度器、驱动和硬件队列。不同位置看到的“延迟”“队列”和“利用率”并不完全相同。
iostat 是观察块设备整体行为的入口,但它不是完整的因果证据。正确诊断通常需要建立这样的证据链:
应用请求
│
├─ 缓存命中或写入页缓存:可能暂时不产生设备 I/O
│
▼
文件系统 / VFS
▼
Bio 与块层队列
▼
设备映射层:LVM、RAID、dm-crypt 等
▼
I/O 调度器与 blk-mq 软件队列
▼
驱动和硬件队列
▼
存储设备
下文中的“磁盘”泛指 Linux 块设备,实际对象可能是 NVMe、SATA SSD、机械硬盘、硬件 RAID、云盘或网络块设备。它们的并发能力、延迟模型和 %util 含义并不相同。
一、先定义四个核心量
1. 吞吐:单位时间完成了多少数据
吞吐量通常表示为字节每秒:
其中:
- 是吞吐量;
- 是采样窗口内完成的读写字节数;
- 是采样窗口长度。
例如,1 秒内完成 262,144,000 字节读操作:
这里使用:
吞吐不是 IOPS。IOPS 是每秒完成的 I/O 请求数:
两者通过平均请求大小关联:
其中 是每个请求传输的平均字节数。
因此:
- 10,000 IOPS、每次 4 KiB,约为 39.1 MiB/s;
- 1,000 IOPS、每次 256 KiB,约为 250 MiB/s。
两者的 IOPS 差异很大,但吞吐量可能与典型 SSD 的顺序带宽相当。只看吞吐会漏掉小块随机 I/O,只看 IOPS 又会漏掉大块顺序 I/O。
2. 延迟:一次请求从开始等待到完成用了多久
“延迟”必须先说明测量边界。常见边界包括:
- 应用延迟:应用发出请求到收到结果;
- 系统调用延迟:进入
read(2)、write(2)或异步 I/O 接口到返回; - 块层延迟:请求进入块层统计点到块请求完成;
- 设备服务时间:设备真正开始处理到完成。
iostat 的 await 是块设备统计意义上的平均请求等待时间,通常包含请求在队列中的等待以及设备处理时间。它不是应用端完整延迟,也不是只表示机械盘寻道时间。
若采样窗口内完成了 个请求,第 个请求的块层耗时为 ,则平均延迟为:
平均值无法展示尾延迟。例如 99 个请求耗时 1 ms、1 个请求耗时 901 ms:
平均延迟只有 10 ms,但 99 分位延迟约为 901 ms。交互式服务往往会被尾延迟影响,而 iostat 默认不会直接给出 P95、P99。
3. 队列深度:同时处于未完成状态的请求数量
队列深度可以理解为某个边界上“已经提交但还没有完成”的 I/O 数量。它不是简单的“磁盘前排了多少个请求”,因为 Linux 中可能同时存在多层队列:
- 进程或运行库中的异步请求;
- 文件系统和页缓存中的脏页;
- 块层软件队列;
blk-mq的每 CPU 或每硬件上下文队列;- 驱动队列;
- 设备内部队列;
- 存储阵列或云服务端队列。
因此必须说明观察层次。iostat 的 avgqu-sz 是采样期间设备统计层面未完成 I/O 数量的平均值,实际显示和计算依赖 sysstat 版本及内核提供的块设备统计数据。它不能直接等同于 NVMe 控制器的每个硬件队列深度,也不能等同于应用的并发请求数。
4. 饱和度:新增需求已经无法被服务能力及时消化
饱和不是“利用率大于某个固定百分比”。更准确地说,若到达速率为 ,设备有效服务能力为 ,则:
- :系统在稳定条件下可以消化请求;
- :队列和延迟对负载变化敏感;
- :未完成请求会持续积累,延迟通常上升。
对于简单单服务台模型,服务时间为 时:
是理想化的忙碌比例。当 接近 1 时,新增请求几乎没有空闲服务时间可用,排队延迟会快速增加。
真实 SSD、NVMe、RAID 和云盘通常是并行服务系统,不能把设备粗略当成一条只有一个服务台的队列。但“到达需求接近或超过有效处理能力时,队列增长、延迟上升”的因果关系仍然成立。
二、iostat 到底测量什么
iostat 通常由 sysstat 软件包提供。现代发行版中可以使用:
iostat -xz 1 10
参数含义:
-x:显示扩展设备统计;-z:隐藏统计窗口内没有活动的设备;1:每 1 秒输出一次;10:输出 10 个采样窗口。
第一次输出常常是系统启动以来的累计平均值,不适合直接当作当前瞬时状态。可以使用:
iostat -xzy 1 10
其中 -y 通常用于跳过第一次报告。具体参数以本机 iostat --help 和 sysstat 版本为准。
典型扩展字段包括:
| 字段 | 含义 |
|---|---|
r/s、w/s |
每秒完成的读、写请求数 |
rkB/s、wkB/s |
每秒完成的读、写数据量 |
rrqm/s、wrqm/s |
每秒被合并的读、写请求数 |
r_await、w_await |
读、写请求的平均等待时间,通常以毫秒计 |
await |
读写请求合计的平均等待时间 |
avgqu-sz |
统计窗口内平均未完成请求数 |
rareq-sz、wareq-sz |
平均读、写请求大小 |
%util |
统计设备处于活动状态的时间比例 |
不同 sysstat 版本字段可能略有差异。旧版本可能显示 avgrq-sz 和 svctm,其中 svctm 在现代内核和多队列设备上并不可靠,不能把它当作当前设备真实服务时间;许多版本已经移除或不再推荐使用。
一个具体的 iostat 结果
假设采样得到:
Device r/s w/s rkB/s wkB/s r_await w_await avgqu-sz await %util
nvme0n1 12000.0 0.0 48000.0 0.0 8.0 0.0 96.0 8.0 99.5
可以这样计算和验证:
-
读 IOPS 为 12,000;
-
读吞吐为 48,000 KiB/s,平均每次读请求约为:
-
平均读延迟为 8 ms;
-
根据 Little 定律:
其中 次/秒, 秒,因此:
这与
avgqu-sz=96一致; -
%util=99.5说明在该统计层,设备几乎整个采样窗口都存在未完成 I/O。
这里的 96 不是说“设备只能同时处理 96 个请求”,而是该采样窗口内平均有约 96 个请求处于未完成状态。它可能跨越软件队列、驱动和设备处理阶段。
三、Little 定律如何帮助判断队列与延迟
Little 定律是:
其中:
- :系统内平均请求数;
- :稳定状态下的平均到达或完成速率;
- :请求在系统内的平均停留时间。
在块设备统计的近似场景中,可以写成:
完整推导如下:
-
设某时间窗口内完成 个请求;
-
第 个请求在统计边界内停留 秒;
-
所有请求累计占用时间为:
-
若窗口长度为 ,则平均并发请求数为:
-
因为:
所以:
完整算例
某 10 秒窗口内完成 50,000 个请求,每个请求平均块层延迟为 20 ms:
因此平均未完成请求数约为 100。
如果请求数不变,但延迟升到 100 ms:
这说明队列变长可能是延迟升高的结果,而不一定是应用主动把并发量提高了。反过来,提高并发量通常会增加设备的排队压力,最终又推高延迟。
使用 Little 定律的边界
这个关系要求统计窗口足够长,并且系统大致处于稳定状态。以下情况会产生偏差:
- 采样刚开始时有大量历史请求尚未完成;
- 设备处于突发负载,窗口太短;
- 观察的是提交速率,而
iostat使用完成速率; - 请求在不同层被合并、拆分或重试;
- 统计边界不同,例如应用层和物理盘层;
- 设备有明显的读写优先级、后台回收或缓存行为。
因此 Little 定律适合做一致性检查,不应被当成把所有层的指标强行相等的证明。
四、延迟是如何被队列“制造”出来的
一个块请求的总时间可以近似拆成:
其中:
software queue:在 Linux 软件队列中等待;dispatch:等待调度器或驱动发出;device queue:在控制器或设备内部等待;service:介质、闪存控制器、阵列等实际处理;retry:超时、错误重试或链路恢复带来的额外时间。
当并发较低时,队列项可能接近零,延迟主要由设备服务时间决定。当并发增加到设备的处理能力附近时,新的请求不能立即获得服务,队列项开始占主导。
这解释了一个常见现象:
低负载:4 KiB 随机读,平均延迟 0.3 ms
高并发:同样是 4 KiB 随机读,平均延迟 5 ms,P99 达到 30 ms
请求大小和读写模式没有变化,但队列等待时间增加了。
机械盘还会受到寻道和旋转等待影响;SSD 可能受到闪存垃圾回收、写放大、内部并行度和温度降速影响;云盘可能受到租户限额、突发积分和后端共享资源影响。因此设备标称延迟不是一个固定常数。
五、如何理解 %util:有用,但不是万能饱和指标
%util 表示在统计窗口中设备存在活动 I/O 的时间比例。对只有一个请求能够有效执行的传统机械设备,这个值接近 100% 往往意味着设备没有空闲时间。
但对现代并行设备,%util=100% 只表示“始终有请求在活动”,不表示设备已经达到绝对性能上限。例如 NVMe 可能同时处理数十或数百个请求:
场景 A:%util=100%,await=0.4 ms,队列稳定,吞吐仍随并发增加
场景 B:%util=100%,await=20 ms,队列持续增长,吞吐基本不再增加
两者都显示 100%,但只有场景 B 明确表现出饱和或接近饱和。
反过来,%util 较低也不能排除性能问题:
- 设备请求是突发的,1 秒采样把短暂高峰平均掉了;
- 进程本身被 CPU、锁、内存回收或网络阻塞,没能持续提交 I/O;
- I/O 在上层排队,还没有到达该设备;
- 使用的是设备映射层,瓶颈在下层物理盘;
- 设备或云服务对请求进行了限速,表现为提交受阻而非设备持续忙碌;
- 文件系统或页缓存改变了实际设备 I/O 形态。
更可靠的饱和判断应同时观察:
- IOPS 和吞吐是否已经接近该工作负载下的稳定上限;
- 并发继续增加时,吞吐是否不再增长;
avgqu-sz是否持续增加或长期偏高;await、r_await、w_await是否明显上升;- 应用端请求延迟和超时是否同步恶化;
- 物理下层设备是否也出现对应压力。
六、队列深度与并发:增加并发不等于提高性能
应用并发量决定了最多可以同时产生多少 I/O,但最终队列深度还受到多个上限约束:
- 进程线程数或异步请求数;
- 文件描述符和运行库限制;
- 文件系统提交策略;
- 块层请求队列;
blk-mq硬件队列数量与深度;- 设备控制器能力;
- cgroup I/O 限制;
- RAID、网络存储或云端服务限制。
在低并发下,设备可能没有被充分利用:
并发 1:1000 IOPS,延迟 1 ms
并发 8:7000 IOPS,延迟 1.1 ms
并发 64:10000 IOPS,延迟 6 ms
并发 256:10000 IOPS,延迟 40 ms
从并发 1 增加到 64,吞吐提高但延迟恶化;从 64 增加到 256,吞吐不再提高,只是让请求在队列中等待更久。这是典型的“容量上限已到,额外并发只制造排队”。
对延迟敏感的服务,目标通常不是把队列填满,而是在吞吐、平均延迟和尾延迟之间选择工作点。这个工作点必须通过实际工作负载测量,不能套用一个固定队列深度。
七、读写比例、请求大小和合并会改变所有指标
1. 请求大小
r/s、w/s 与 rkB/s、wkB/s 必须一起看。平均请求大小可以近似计算:
例如:
r/s = 2000
rkB/s = 8000
则:
若 r/s=2000 但 rkB/s=512000,平均读请求约为 256 KiB。两种负载的 IOPS 相同,设备压力可能完全不同。
2. 请求合并
rrqm/s 和 wrqm/s 表示请求在块层被合并的数量。合并可能降低设备需要处理的请求数,也可能把相邻小请求变成更大的请求。
但不能简单地认为“合并越多越好”:
- 顺序写的合并可能提高吞吐;
- 交互式随机读可能没有合并机会;
- 现代 SSD 对随机访问的处理方式与机械盘不同;
- 合并前后的统计边界不同,不能直接拿应用写入次数与设备完成次数比较。
3. 缓存和写回
Buffered I/O 可能只修改页缓存,write() 很快返回,真正的设备写入由内核稍后通过 writeback 发出。于是:
应用 write 延迟低
但稍后 iostat 的 wkB/s、w/s 和 w_await 上升
这不是矛盾,而是两个观测点不同。
读操作也可能命中页缓存,应用完成了一次读取,但物理设备没有产生对应读请求。要诊断真实存储压力,必须区分:
- 应用逻辑读写量;
- 文件系统和页缓存行为;
- 块设备实际完成量。
O_DIRECT 或数据库的 direct I/O 可以减少页缓存影响,但它要求缓冲区对齐、长度和偏移满足实现约束,且不会自动保证低延迟。直接 I/O 仍然会经过文件系统、块层、驱动和设备队列。
八、从逻辑设备追到物理设备
服务器上常见的设备链路可能是:
应用
▼
/dev/mapper/vg-data-lv
▼
LVM / device-mapper
▼
/dev/md0
▼
RAID 成员盘
对逻辑设备运行:
iostat -xzy 1 5
只能说明逻辑设备层面的统计。为了确认物理瓶颈,还应查看设备关系:
lsblk -o NAME,TYPE,SIZE,FSTYPE,MOUNTPOINTS,PKNAME
findmnt -T /path/to/data
cat /sys/block/nvme0n1/queue/scheduler
其中:
findmnt -T用于确认路径实际挂载在哪个文件系统;lsblk用于查看分区、LVM、RAID 等关系;queue/scheduler可查看当前块设备调度器,具体可选项依内核和设备而异。
如果逻辑设备 dm-0 的 await 很高,还要观察其下层物理设备:
dm-0 await=30 ms %util=100%
nvme0n1 await=2 ms %util=60%
nvme1n1 await=2 ms %util=55%
这可能意味着逻辑层存在映射、限速、串行化或上层排队问题,而不是物理盘本身已经饱和。相反,如果多个下层盘同时出现高延迟和高队列,瓶颈更可能在物理介质、阵列或共享后端。
设备映射还会改变请求数量:一个逻辑写请求可能被 RAID 分解成多个物理请求;加密、校验和重试也可能改变下层表现。因此不同层的 IOPS 不应机械相加或直接比较。
九、一个可重复的诊断流程
第一步:确认负载和设备对象
findmnt -T /var/lib/mydb
lsblk -o NAME,TYPE,SIZE,FSTYPE,MOUNTPOINTS,PKNAME
先确认业务路径、文件系统、逻辑卷和物理设备。否则很容易盯着没有承载业务数据的磁盘分析。
第二步:观察短时间动态数据
iostat -xzy 1 10
重点记录:
- 读写 IOPS;
- 读写吞吐;
- 平均请求大小;
r_await、w_await;avgqu-sz;%util;- 逻辑设备与物理设备是否同时异常。
不要只截取一次输出。一次输出无法区分稳定趋势和瞬时尖峰。
第三步:把设备压力归因到进程
pidstat -d -p ALL 1 10
pidstat 可以显示进程级的读写速率和相关延迟统计,字段同样受 sysstat 版本影响。它回答的是“哪个进程产生或等待 I/O”,但对于页缓存写回,发起脏页修改的进程与最终执行写回的内核线程可能并不相同。
还可以配合:
vmstat 1 10
观察运行队列、内存回收和阻塞进程。若磁盘指标异常,但 CPU 大量时间用于内存回收或系统调用,问题可能是内存压力导致的 I/O 放大,而不是单纯设备容量不足。
第四步:确认是否是延迟问题而非带宽问题
假设观测到:
r/s = 3000
rkB/s = 12000
r_await = 25 ms
avgqu-sz = 75
%util = 95%
平均请求大小为 4 KiB,理论吞吐只有约 11.7 MiB/s,但延迟已经达到 25 ms。此时更像是小块随机 I/O 或队列等待问题,而不是顺序带宽耗尽。
反过来:
r/s = 1000
rkB/s = 262144
r_await = 2 ms
avgqu-sz = 2
%util = 100%
平均请求大小约为 256 KiB,吞吐约 256 MiB/s。此时设备持续忙,但队列和延迟不高,可能是顺序带宽工作点,而不应仅因为 %util=100% 就判定系统异常。
第五步:需要时采集请求级证据
iostat 是聚合统计。如果需要知道“请求在哪一层等待”,可以使用内核块层跟踪能力,例如:
blktrace;- BCC 中的块 I/O 工具;
- 基于 eBPF 的块请求延迟工具;
block:block_rq_issue和block:block_rq_complete等内核 tracepoint。
典型思路是记录同一请求在 issue 和 complete 事件之间的时间:
再按设备、进程、读写方向和请求大小聚合出直方图,才能观察 P50、P95、P99 等尾延迟。
这些工具通常需要 root 或相应的 tracing 权限,生产环境中可能带来 CPU、内存和日志开销。应缩短采样时间、限制设备和进程范围,并先在低风险环境验证。不同发行版对 BCC、bpftrace、内核配置和 tracepoint 的可用性不同,不能假设所有系统都预装。
十、用 fio 做基准测试时的边界
当需要区分“设备能力不足”和“业务负载形态不合适”时,可以用 fio 构造受控负载。为了避免直接覆盖生产数据,可以对预先创建的测试文件执行只读测试:
fio --name=randread \
--filename=/data/testfile \
--size=4G \
--rw=randread \
--bs=4k \
--ioengine=libaio \
--iodepth=32 \
--direct=1 \
--runtime=60 \
--time_based \
--group_reporting
主要参数含义:
--filename:测试文件路径;--size:测试范围;--rw=randread:随机读;--bs=4k:每次请求 4 KiB;--iodepth=32:异步队列深度目标;--direct=1:尽量绕过页缓存;--runtime=60 --time_based:持续测试 60 秒;--group_reporting:合并线程或作业输出。
这个命令的前置条件是 /data/testfile 已经存在并且其中没有需要保护的数据。randread 不会写入该文件,但文件创建、预热和文件系统状态仍然会影响结果。不能把生产挂载点、数据库文件或未确认的块设备直接交给带有写入模式的 fio。
还应注意:
iodepth=32是 fio 作业希望维持的异步深度,不保证每一层都存在 32 个硬件请求;libaio的实际行为会受文件系统和 direct I/O 影响;- 现代 Linux 也常使用
io_uring,但测试结果仍取决于内核、fio 版本和参数; - 测试文件可能受到文件系统分配、碎片和设备缓存影响;
- 云盘的限额、突发性能和测试持续时间会显著影响结果;
- 测试会消耗真实 IOPS、带宽和缓存资源,生产环境应设置时间、范围和并发上限。
基准测试的价值在于比较同一环境下不同请求大小、读写比例和队列深度的变化,不应直接把某个实验数字当作所有业务的承诺容量。
十一、常见误判及其反例
误判一:%util=100% 就一定是故障
反例:
设备类型:高并行 NVMe
IOPS:正常
吞吐:正常
await:0.5 ms
avgqu-sz:稳定
应用 P99:正常
%util:100%
这可能只是设备持续有请求,没有证据表明请求已经排队恶化。应继续增加受控负载并观察吞吐是否达到平台,不能单凭 %util 报警。
误判二:await 高就一定是设备服务时间高
反例:
设备服务时间:2 ms
软件队列等待:18 ms
await:20 ms
await 是总等待的聚合结果。高值可能来自上层提交过多、设备内部队列、调度器策略、cgroup 限流或下层映射,而不是介质本身处理慢。
误判三:吞吐低就说明磁盘没有忙
反例:
4 KiB 随机读:20000 IOPS
吞吐:约 78 MiB/s
await:15 ms
小块随机 I/O 的吞吐天然可能不高,但请求数和延迟都足以让数据库或元数据服务变慢。必须同时看请求大小、IOPS 和延迟。
误判四:应用没有 write() 阻塞,所以磁盘没有写压力
Buffered write 可能只写入页缓存。应用返回快并不表示数据已经持久化;稍后 writeback 可能形成高峰,甚至使其他进程在脏页比例或写回压力下阻塞。需要结合 iostat、内存回收、文件系统和应用的持久化语义判断。
误判五:只查看挂载点对应的逻辑设备
如果数据位于 LVM、RAID 或加密层,逻辑设备的指标只是链路中的一个观察点。必须沿设备拓扑向下检查,否则可能把下层物理盘问题误归因于文件系统,或把映射层限速误认为硬盘故障。
十二、把指标组合成因果判断
可以用以下几类组合建立初步假设:
高 await、高 avgqu-sz、高 %util
通常表示设备或其下层接近饱和,新增请求主要消耗在排队。下一步应确认:
- 是读还是写;
- 请求大小和读写比例;
- 逻辑设备下层是否同样繁忙;
- 应用端是否出现超时和尾延迟;
- 是否存在 cgroup 或云盘限额。
高吞吐、低或中等延迟、队列稳定
更像是顺序带宽工作负载,设备持续工作但没有明显排队增长。若业务目标是批量传输,这可能是正常状态;若业务是低延迟随机读,则还需要看请求大小和尾延迟。
低 %util、高 await
不能直接下结论。需要缩短采样周期并检查:
- 是否是突发 I/O;
- 上层是否有大量等待但请求尚未下发;
- 是否发生错误重试;
- 是否是逻辑设备、网络块设备或映射层统计;
- 进程是否被 CPU、内存或锁阻塞。
读写方向指标不对称
如果 w_await 明显高于 r_await,可能是写回、写放大、RAID 校验、设备垃圾回收或同步写语义造成。不能因为读延迟正常,就认为磁盘整体正常。
十三、生产环境中的验证与恢复
采集 iostat、pidstat 和 /proc/diskstats 通常风险较低,但高频 tracing、blktrace 和大规模 eBPF 采集会增加开销。诊断动作应当:
- 先进行无侵入聚合观测;
- 缩短高开销工具的运行时间;
- 限定设备、进程和事件类型;
- 记录采样开始时间、业务流量和配置变化;
- 修改调度器、队列深度、I/O 优先级或限速参数前保存原值;
- 修改后重新采样,确认延迟、吞吐和错误率是否改善;
- 恢复配置并验证业务,而不是只观察某一个指标下降。
不要在不了解业务的情况下执行 drop_caches、直接对块设备运行写入型 fio、调整数据库文件所在设备的队列参数,或为了“清空队列”重启服务。清理页缓存会造成全机缓存抖动;写入型基准测试可能直接破坏数据;队列和调度器参数在不同设备上可能有相反效果。
磁盘诊断的最终目标不是让某个数字看起来更低,而是解释完整链路:
业务请求模式
→ 实际 IOPS 与请求大小
→ 块层队列和设备并发
→ 吞吐、平均延迟与尾延迟
→ 队列是否持续增长
→ 设备或映射层是否达到有效容量
→ 应用是否真正受到影响
iostat 负责提供设备级时间窗口统计;Little 定律帮助校验 IOPS、延迟和队列之间是否一致;拓扑工具负责确认逻辑设备与物理设备关系;进程级工具负责归因;块层跟踪和 eBPF 负责在需要时定位请求究竟停留在哪一层。只有把这些证据放在同一条 I/O 路径上,才能区分带宽瓶颈、IOPS 瓶颈、排队延迟、写回压力、映射层限速和真正的设备故障。
系列导航与关联阅读
- 系列入口:Linux 完整学习路线:从内核与文件系统到网络、性能和生产运维
- 上一篇:Linux NFS:协议、挂载、缓存、一致性、权限和高可用
- 下一篇:Linux 磁盘加密:dm-crypt、LUKS、密钥槽、启动解锁和恢复
- 延伸:Linux 块 I/O 栈:Bio、Queue、Scheduler、Direct I/O 和延迟
- 延伸:Linux 性能分析方法:CPU、内存、磁盘、网络与 eBPF 证据链
官方资料
本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。

评论
0 条讨论