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. 吞吐:单位时间完成了多少数据

吞吐量通常表示为字节每秒:

B=ΔbytesΔtB = \frac{\Delta bytes}{\Delta t}

其中:

  • BB 是吞吐量;
  • Δbytes\Delta bytes 是采样窗口内完成的读写字节数;
  • Δt\Delta t 是采样窗口长度。

例如,1 秒内完成 262,144,000 字节读操作:

B=262,144,000 B/s250 MiB/sB = 262{,}144{,}000 \text{ B/s} \approx 250 \text{ MiB/s}

这里使用:

1 MiB=10242 bytes1\text{ MiB}=1024^2\text{ bytes}

吞吐不是 IOPS。IOPS 是每秒完成的 I/O 请求数:

X=ΔrequestsΔtX = \frac{\Delta requests}{\Delta t}

两者通过平均请求大小关联:

BX×SB \approx X \times S

其中 SS 是每个请求传输的平均字节数。

因此:

  • 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 接口到返回;
  • 块层延迟:请求进入块层统计点到块请求完成;
  • 设备服务时间:设备真正开始处理到完成。

iostatawait 是块设备统计意义上的平均请求等待时间,通常包含请求在队列中的等待以及设备处理时间。它不是应用端完整延迟,也不是只表示机械盘寻道时间。

若采样窗口内完成了 NN 个请求,第 ii 个请求的块层耗时为 RiR_i,则平均延迟为:

Rˉ=i=1NRiN\bar R = \frac{\sum_{i=1}^{N}R_i}{N}

平均值无法展示尾延迟。例如 99 个请求耗时 1 ms、1 个请求耗时 901 ms:

Rˉ=99×1+901100=10 ms\bar R=\frac{99\times1+901}{100}=10\text{ ms}

平均延迟只有 10 ms,但 99 分位延迟约为 901 ms。交互式服务往往会被尾延迟影响,而 iostat 默认不会直接给出 P95、P99。

3. 队列深度:同时处于未完成状态的请求数量

队列深度可以理解为某个边界上“已经提交但还没有完成”的 I/O 数量。它不是简单的“磁盘前排了多少个请求”,因为 Linux 中可能同时存在多层队列:

  • 进程或运行库中的异步请求;
  • 文件系统和页缓存中的脏页;
  • 块层软件队列;
  • blk-mq 的每 CPU 或每硬件上下文队列;
  • 驱动队列;
  • 设备内部队列;
  • 存储阵列或云服务端队列。

因此必须说明观察层次。iostatavgqu-sz 是采样期间设备统计层面未完成 I/O 数量的平均值,实际显示和计算依赖 sysstat 版本及内核提供的块设备统计数据。它不能直接等同于 NVMe 控制器的每个硬件队列深度,也不能等同于应用的并发请求数。

4. 饱和度:新增需求已经无法被服务能力及时消化

饱和不是“利用率大于某个固定百分比”。更准确地说,若到达速率为 λ\lambda,设备有效服务能力为 μ\mu,则:

  • λ<μ\lambda < \mu:系统在稳定条件下可以消化请求;
  • λμ\lambda \approx \mu:队列和延迟对负载变化敏感;
  • λ>μ\lambda > \mu:未完成请求会持续积累,延迟通常上升。

对于简单单服务台模型,服务时间为 SS 时:

μ1S,U=λS\mu \approx \frac{1}{S},\qquad U=\lambda S

UU 是理想化的忙碌比例。当 UU 接近 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/sw/s 每秒完成的读、写请求数
rkB/swkB/s 每秒完成的读、写数据量
rrqm/swrqm/s 每秒被合并的读、写请求数
r_awaitw_await 读、写请求的平均等待时间,通常以毫秒计
await 读写请求合计的平均等待时间
avgqu-sz 统计窗口内平均未完成请求数
rareq-szwareq-sz 平均读、写请求大小
%util 统计设备处于活动状态的时间比例

不同 sysstat 版本字段可能略有差异。旧版本可能显示 avgrq-szsvctm,其中 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

可以这样计算和验证:

  1. 读 IOPS 为 12,000;

  2. 读吞吐为 48,000 KiB/s,平均每次读请求约为:

    48,000 KiB/s12,000 IOPS=4 KiB\frac{48{,}000\text{ KiB/s}}{12{,}000\text{ IOPS}}=4\text{ KiB}

  3. 平均读延迟为 8 ms;

  4. 根据 Little 定律:

    LX×RL \approx X \times R

    其中 X=12,000X=12{,}000 次/秒,R=0.008R=0.008 秒,因此:

    L12,000×0.008=96L\approx12{,}000\times0.008=96

    这与 avgqu-sz=96 一致;

  5. %util=99.5 说明在该统计层,设备几乎整个采样窗口都存在未完成 I/O。

这里的 96 不是说“设备只能同时处理 96 个请求”,而是该采样窗口内平均有约 96 个请求处于未完成状态。它可能跨越软件队列、驱动和设备处理阶段。

三、Little 定律如何帮助判断队列与延迟

Little 定律是:

L=λWL=\lambda W

其中:

  • LL:系统内平均请求数;
  • λ\lambda:稳定状态下的平均到达或完成速率;
  • WW:请求在系统内的平均停留时间。

在块设备统计的近似场景中,可以写成:

平均未完成 I/OIOPS×平均块层延迟\text{平均未完成 I/O} \approx \text{IOPS} \times \text{平均块层延迟}

完整推导如下:

  1. 设某时间窗口内完成 NN 个请求;

  2. ii 个请求在统计边界内停留 RiR_i 秒;

  3. 所有请求累计占用时间为:

    Toccupied=i=1NRiT_{\text{occupied}}=\sum_{i=1}^{N}R_i

  4. 若窗口长度为 TT,则平均并发请求数为:

    L=ToccupiedTL=\frac{T_{\text{occupied}}}{T}

  5. 因为:

    λ=NT,Rˉ=RiN\lambda=\frac{N}{T},\qquad \bar R=\frac{\sum R_i}{N}

    所以:

    L=NT×RiN=λRˉL=\frac{N}{T}\times\frac{\sum R_i}{N} =\lambda\bar R

完整算例

某 10 秒窗口内完成 50,000 个请求,每个请求平均块层延迟为 20 ms:

λ=50,00010=5,000 IOPS\lambda=\frac{50{,}000}{10}=5{,}000\text{ IOPS}

L=5,000×0.020=100L=5{,}000\times0.020=100

因此平均未完成请求数约为 100。

如果请求数不变,但延迟升到 100 ms:

L=5,000×0.100=500L=5{,}000\times0.100=500

这说明队列变长可能是延迟升高的结果,而不一定是应用主动把并发量提高了。反过来,提高并发量通常会增加设备的排队压力,最终又推高延迟。

使用 Little 定律的边界

这个关系要求统计窗口足够长,并且系统大致处于稳定状态。以下情况会产生偏差:

  • 采样刚开始时有大量历史请求尚未完成;
  • 设备处于突发负载,窗口太短;
  • 观察的是提交速率,而 iostat 使用完成速率;
  • 请求在不同层被合并、拆分或重试;
  • 统计边界不同,例如应用层和物理盘层;
  • 设备有明显的读写优先级、后台回收或缓存行为。

因此 Little 定律适合做一致性检查,不应被当成把所有层的指标强行相等的证明。

四、延迟是如何被队列“制造”出来的

一个块请求的总时间可以近似拆成:

Rtotal=Rsoftware queue+Rdispatch+Rdevice queue+Rservice+RretryR_{\text{total}} = R_{\text{software queue}} + R_{\text{dispatch}} + R_{\text{device queue}} + R_{\text{service}} + R_{\text{retry}}

其中:

  • 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 形态。

更可靠的饱和判断应同时观察:

  1. IOPS 和吞吐是否已经接近该工作负载下的稳定上限;
  2. 并发继续增加时,吞吐是否不再增长;
  3. avgqu-sz 是否持续增加或长期偏高;
  4. awaitr_awaitw_await 是否明显上升;
  5. 应用端请求延迟和超时是否同步恶化;
  6. 物理下层设备是否也出现对应压力。

六、队列深度与并发:增加并发不等于提高性能

应用并发量决定了最多可以同时产生多少 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/sw/srkB/swkB/s 必须一起看。平均请求大小可以近似计算:

Sr=rkB/sr/sS_r=\frac{rkB/s}{r/s}

例如:

r/s     = 2000
rkB/s   = 8000

则:

Sr=80002000=4 KiBS_r=\frac{8000}{2000}=4\text{ KiB}

r/s=2000rkB/s=512000,平均读请求约为 256 KiB。两种负载的 IOPS 相同,设备压力可能完全不同。

2. 请求合并

rrqm/swrqm/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-0await 很高,还要观察其下层物理设备:

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_awaitw_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_issueblock:block_rq_complete 等内核 tracepoint。

典型思路是记录同一请求在 issue 和 complete 事件之间的时间:

Rblock=tcompletetissueR_{\text{block}}=t_{\text{complete}}-t_{\text{issue}}

再按设备、进程、读写方向和请求大小聚合出直方图,才能观察 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

还应注意:

  1. iodepth=32 是 fio 作业希望维持的异步深度,不保证每一层都存在 32 个硬件请求;
  2. libaio 的实际行为会受文件系统和 direct I/O 影响;
  3. 现代 Linux 也常使用 io_uring,但测试结果仍取决于内核、fio 版本和参数;
  4. 测试文件可能受到文件系统分配、碎片和设备缓存影响;
  5. 云盘的限额、突发性能和测试持续时间会显著影响结果;
  6. 测试会消耗真实 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 校验、设备垃圾回收或同步写语义造成。不能因为读延迟正常,就认为磁盘整体正常。

十三、生产环境中的验证与恢复

采集 iostatpidstat/proc/diskstats 通常风险较低,但高频 tracing、blktrace 和大规模 eBPF 采集会增加开销。诊断动作应当:

  1. 先进行无侵入聚合观测;
  2. 缩短高开销工具的运行时间;
  3. 限定设备、进程和事件类型;
  4. 记录采样开始时间、业务流量和配置变化;
  5. 修改调度器、队列深度、I/O 优先级或限速参数前保存原值;
  6. 修改后重新采样,确认延迟、吞吐和错误率是否改善;
  7. 恢复配置并验证业务,而不是只观察某一个指标下降。

不要在不了解业务的情况下执行 drop_caches、直接对块设备运行写入型 fio、调整数据库文件所在设备的队列参数,或为了“清空队列”重启服务。清理页缓存会造成全机缓存抖动;写入型基准测试可能直接破坏数据;队列和调度器参数在不同设备上可能有相反效果。

磁盘诊断的最终目标不是让某个数字看起来更低,而是解释完整链路:

业务请求模式
  → 实际 IOPS 与请求大小
  → 块层队列和设备并发
  → 吞吐、平均延迟与尾延迟
  → 队列是否持续增长
  → 设备或映射层是否达到有效容量
  → 应用是否真正受到影响

iostat 负责提供设备级时间窗口统计;Little 定律帮助校验 IOPS、延迟和队列之间是否一致;拓扑工具负责确认逻辑设备与物理设备关系;进程级工具负责归因;块层跟踪和 eBPF 负责在需要时定位请求究竟停留在哪一层。只有把这些证据放在同一条 I/O 路径上,才能区分带宽瓶颈、IOPS 瓶颈、排队延迟、写回压力、映射层限速和真正的设备故障。


系列导航与关联阅读

官方资料

本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。