这一章在做什么
回答一个问题:慢是不是因为磁盘,如果是,慢在哪一层、是谁发的 IO。先把一次 IO 的时间分清:在系统队列里等的、在盘上服务的;再用 USE 方法看每块盘的利用率、饱和度和错误,用负载特征描述 IO 的次数、大小、随机还是顺序;最后用延迟分布和逐个 IO 的跟踪找出异常值。最该带走的是 iostat -x 的读法,以及为什么 %util 到了 100% 的 SSD 可能离上限还很远。
什么时候翻这一章
- iowait 很高,或者读写文件慢,想确认是不是盘先分清应用有没有真的在等盘,再看是哪块盘、忙到什么程度。§9.3.11§9.6.1场景手册 →
- iostat 里 %util 到了 100%,盘是不是满了机械盘多半是;SSD 和虚拟盘不一定,要结合 await 和队列长度看。§9.3.9§9.3.10场景手册 →
- 平均延迟正常,偶尔有请求卡几十毫秒平均值把双峰和异常值抹平了,换成延迟直方图和逐个 IO 的跟踪。§9.6.6§9.6.7场景手册 →
- 不知道是哪个进程、哪段代码在写盘按进程看读写量,再用带调用栈的跟踪找到发 IO 的路径,包括文件系统自己的后台任务。§9.6.4§9.6.8§9.6.9
- 板子上的 eMMC 或 SD 卡写日志一卡一卡的闪存怕小块写;看 IO 大小和每秒次数,攒批写往往比换卡管用。§9.3.6§9.4.1场景手册 →
- 要选盘、算要几块盘,或者压测一块盘的上限先说清负载(大小、随机还是顺序、读写比、并发),再按目标利用率算。§9.5.9§9.5.10场景手册 →
上一章(文件系统)讲应用看到的 IO,这一章讲落到盘上的 IO;两者的数量和大小常常对不上,原因在 §9.3.13 和 §8.3.12。利用率到 60% 就开始排队的道理来自第 2 章的排队论(§2.6.5)。perf、BPF 工具的细节在第 13、15 章。
先用中文过一遍
一次 IO 的时间:从哪里开始算
一次 IO 的时间分两种:在队列里等的(wait time),和真正被处理的(service time)。问题在于「从哪里开始算」。书里分了两套说法(§9.3.1):从内核看,IO 先在块层的队列里等,下发给盘以后到完成中断回来是「块 IO 服务时间」,两段加起来是「块 IO 请求时间」;从盘看,IO 先在盘自己的队列里等,再被服务,两段加起来正好等于内核眼里的块 IO 服务时间。
这就是为什么各个工具报的延迟对不上:iostat 的 await 从 IO 进入块层算起,包括在系统队列里等的时间;biolatency 默认从下发给盘算起,加 -Q 才把系统队列算进去;应用自己计时,还要再加上系统调用和文件系统的开销。老版本 iostat 有一列 svctm,用利用率除以 IOPS 推算服务时间,前提是盘一次只处理一个 IO;今天的盘能同时处理很多个,这个算法不再准,新版本已经删了这一列(§9.6.2)。还要记住:盘内缓存命中和未命中的延迟差一到两个数量级,平均值是两个峰的折中,谁也代表不了(§9.3.2)。
利用率和饱和度:%util 为什么在 SSD 上会骗人
iostat 的 %util 是按时间算的利用率:这一秒里,有多少时间盘上至少挂着一个没完成的 IO(§9.3.9)。对一次只能服务一个 IO 的设备,这个定义很好用:100% 就是满了,再来的只能排队。而且机械盘不用等到 100%:书里用第 2 章的 M/D/1 模型说明,利用率过了 60%,平均响应时间就是空闲时的两倍(§2.6.5),所以 60% 左右就可能开始影响延迟。
但 SSD 内部有很多个闪存通道,NVMe 还有多个硬件队列,能同时处理许多 IO;存储阵列和 RAID 虚拟盘背后是多块盘。对它们,100% 只说明「一直有 IO 在飞」,不说明「接不下了」。第 2 章说得更直白:忙了 100% 的时间,不等于用了 100% 的能力(§2.3.11)。sysstat 自己的 iostat 手册也写明了这一点。本站在 WSL2 上实测:一个 dd 用 4 KB 直接 IO 写,%util 约 70%、每秒约 1.1 万次;四个 dd 同时写,每秒到了 4.4 万次,%util 只涨到 95% 左右。
所以判断饱和,要看排队:aqu-sz 是平均有多少个请求在系统队列里等或正在盘上(§9.6.1),并且看 await 有没有随着负载往上走。aqu-sz 远大于盘能并行的数量、await 跟着涨,才是真饱和。还有一层:利用率是区间平均,这一秒 50% 可能是前半秒满载、后半秒空闲(§9.3.10)。
IOPS 不能直接比:大小、随机、读写
吞吐等于 IOPS 乘以平均 IO 大小。大 IO 吞吐高,但每次更慢;小 IO 次数多,每次都要付一遍固定开销(§9.3.6)。所以「这块盘能跑 5 万 IOPS」单独说没有意义,要连同大小、随机还是顺序、读还是写、同时有几个 IO 一起说(§9.3.7)。
机械盘最怕随机:每次 IO 前磁头要先寻道,再等盘片转到那个扇区,两样都是毫秒级,而传 4 KB 数据只要几十微秒。顺序 IO 省掉了这两步,所以同一块盘,顺序和随机的 IOPS 可以差几十倍(§9.4.1)。闪存没有机械动作,随机读和顺序读基本一样(§9.3.4);它怕的是小块写:闪存只能按整块擦除再写,比擦除块小的写会引起额外的搬移,这叫写放大(write amplification)。书里举了个例子:有的闪存盘可能在 4 KB 读、1 MB 写时表现最好,不同的盘要查厂商资料或自己测(§9.3.6)。
iowait:不是盘的指标
iowait 是 CPU 的一种空闲:CPU 闲着,同时有线程在等磁盘 IO(§9.3.11)。它把空闲分成「真没事干」和「在等盘」两种,所以高 iowait 确实提示盘可能是瓶颈。但它会被 CPU 负载左右:来了一个吃 CPU 的进程,CPU 不闲了,iowait 就降下去,盘上的问题一点没变;反过来,应用升级后更省 CPU,iowait 反而冒出来,看着像新版本把盘搞慢了。
更可靠的是直接看线程被 IO 挡住了多久:/proc/pressure/io(PSI)给出有任务因等 IO 而停顿的时间比例(§9.6.3),pidstat -d 的 iodelay 按进程给出阻塞的时钟滴答数(§9.6.4)。另外,盘慢不一定让应用慢:写进 page cache 就返回的写、文件系统的预读、专门的 IO 线程,都和应用的请求是异步的(§9.3.12)。反过来,应用的一次读写到了盘上可能变成好几次 IO(元数据、日志),也可能一次都没有(命中缓存)(§9.3.13)。
| 英文 | 中文 | 一句话 |
|---|---|---|
| IOPS | 每秒 IO 次数 | 单独一个数没意义,要带上大小、随机与否、读写 §9.3.7 术语页 |
| throughput / bandwidth | 吞吐 / 带宽 | 吞吐是当前每秒传多少字节,带宽是硬件能到的上限 §9.1 术语页 |
| await | 平均请求时间 | iostat 的一列,含系统队列和设备上的时间,单位毫秒 §9.6.1 术语页 |
| service time / wait time | 服务时间 / 等待时间 | 被处理的时间和排队的时间;从内核看和从盘看,分界不同 §9.3.1 |
| utilization (%util) | 利用率 | 区间里盘上至少有一个 IO 的时间比例,不是能力的比例 §9.3.9 术语页 |
| saturation (aqu-sz) | 饱和度(平均队列长度) | 超出处理能力而排着的工作;iostat 里看 aqu-sz §9.3.10 术语页 |
| I/O wait | IO 等待 | CPU 空闲且有 IO 未完成的时间,会被 CPU 负载挤掉 §9.3.11 术语页 |
| random / sequential I/O | 随机 / 顺序 IO | 按相邻两次 IO 在盘上的位置是否接续来分 §9.3.4 术语页 |
| latency outlier | 延迟异常值 | 延迟远高于大多数的个别 IO,平均值里看不见 §9.1 术语页 |
| write-back cache | 回写缓存 | 数据进了缓存就报完成,稍后再写到介质;对应的是直写 §9.2.2 术语页 |
| write amplification | 写放大 | 闪存按块擦除,小块写引起额外的搬移和写入 §9.4.1 术语页 |
| FTL (flash translation layer) | 闪存转换层 | SSD 控制器里把扇区读写翻译成闪存页和擦除块的那层 §9.4.1 |
| I/O scheduler / blk-mq | IO 调度器 / 多队列块层 | 块层里给 IO 排序、合并的部件;blk-mq 按 CPU 分提交队列 §9.4.4 术语页 |
| PSI (pressure stall information) | 压力停顿信息 | io 一项给出有任务因等 IO 而停顿的时间比例 §9.6.3 术语页 |
| direct I/O | 直接 IO | 用 O_DIRECT 绕过 page cache;测盘本身时要用它 §9.8.1 术语页 |
方法:按什么顺序查
书里列了十种磁盘的方法,作者推荐的顺序是:USE 方法、性能监控、负载特征、延迟分析、微基准测试、静态分析、事件跟踪(§9.5)。落到一次排查上,本站整理成下面几步:前三步用 sysstat 和 /proc 就能做完,第四步开始要 BPF 工具,多数要 root。
- 应用真的在等盘吗?
cat /proc/pressure/io看 IO 停顿比例和趋势;vmstat 1的b列是阻塞在 IO 上的线程数;pidstat -d 1的iodelay按进程看等了多久。iowait 只当线索。都不高,就别在盘上花时间。§9.3.11§9.6.3 - 哪块盘,忙不忙,排不排队,有没有错(USE)
iostat -sxz 1:按盘看await、aqu-sz、%util。机械盘 %util 持续 60% 以上就可能在排队;SSD 和虚拟盘要看 await 是否上涨。错误先看dmesg里的 IO 报错和smartctl,盘配成冗余时,坏了一块系统照样能跑,只是变慢,容易被忽略。§9.5.2§9.6.1 - 负载长什么样:多少次、多大、读还是写、随机还是顺序
iostat -dx 1把读写分开:r/s、w/s、rkB/s、wkB/s、rareq-sz。r_await通常比w_await更直接关系到应用。合并数rrqm/s为零、平均大小 8 KB 以下,多半是随机的小 IO。再用pidstat -d或biotop找出是谁。§9.5.4§9.6.8 - 延迟的分布:有没有双峰、有没有异常值
biolatency -F按读、写、同步写等标志分开画直方图,-D按盘分开;看到几十毫秒的桶,再用biosnoop把那些 IO 一个个找出来,看它们前面是不是刚来过一批 IO(排队),还是孤零零地慢(盘或宿主的问题)。§9.6.6§9.6.7 - 慢在哪一层?
把应用量到的延迟、
biolatency -Q(含块层队列)和默认的biolatency(只算盘)放在一起比:三者接近,就是盘慢;应用慢而盘不慢,问题在文件系统或更上面(锁、元数据),回到第 8 章。§9.5.5 - 这些 IO 为什么会发?
biostacks或perf record -e block:block_rq_issue -a -g看发 IO 的调用栈。常见的意外:文件系统日志、后台校验、路径查找读目录、内存不够引起的换页。§9.6.5§9.6.9 - 配置和上限
几块盘、什么类型、RAID 怎么配、调度器是什么(
/sys/block/*/queue/scheduler)、有没有 cgroup 的 IO 限额;不知道盘的上限,就在空闲时用fio或dd oflag=direct测一遍,作为以后判断「满没满」的基准。§9.5.6§9.5.9
工具速查
| 想知道 | 敲什么 | 看哪里 | 节 |
|---|---|---|---|
| IO 压力的趋势 | cat /proc/pressure/io | some avg10 比 avg300 高很多,说明压力在上升;full 是所有非空闲任务同时卡在 IO 上的时间比例 | §9.6.3 |
| 每块盘的概况 | iostat -sxz 1 | await 是平均每次 IO 的总时间;aqu-sz 是队列长度;%util 在 SSD 和虚拟盘上只代表忙碌时间 | §9.6.1 |
| 读写分开 | iostat -dx 1 | r_await 和 w_await 分开看;rareq-sz 小、rrqm/s 为零是随机小 IO;f/s 是刷缓存命令 | §9.6.1 |
| 过去的磁盘统计 | sar -d 1 | 和 iostat 相近的列(tps、rkB/s、wkB/s、areq-sz、aqu-sz、await、%util),await 不分读写;配了 sysstat 定时采集,就能翻到出事那个时段 | §9.6.2 |
| 哪个进程在读写 | pidstat -d 1 | kB_rd/s、kB_wr/s;iodelay 是等 IO 的时钟滴答数(新内核要先打开 delay accounting,见「成书之后」) | §9.6.4 |
| 按进程排的磁盘 top | biotop-bpfcciotop -o | 每个进程的 IO 次数、字节数、平均延迟;PID 是尽力而为的,IO 下发时发起者可能已经不在 CPU 上 | §9.6.8 |
| 延迟的分布 | biolatency-bpfcc -m 10 1biolatency-bpfcc -F 10 1 | 有没有两个峰、最右边的桶到了多少毫秒;-F 按 IO 标志拆开,常能看出慢的是写 | §9.6.6 |
| 每一个 IO | biosnoop-bpfcc -Q | 进程、读写、扇区、大小、LAT(ms);QUE(ms) 是在系统队列里的时间。存成文件,按延迟列排序看最慢的几行 | §9.6.7 |
| 是哪条代码路径发的 IO | biostacks.btperf record -e block:block_rq_issue -a -g -- sleep 10 | 内核栈指向文件系统的哪个操作;IO 被内核线程下发时,换成 block:block_rq_insert 更容易看到发起者 | §9.6.9§9.6.5 |
| IO 在块层各阶段的耗时 | btrace /dev/sdabtt -i 文件 | 每个 IO 的排队、合并、下发、完成各一行;btt 的 D2C 是盘上的时间,Q2C 是块层的全程 | §9.6.10 |
| 盘的健康和错误 | smartctl -a /dev/sdadmesg | grep -i error | 纠正过的错误数、重映射的扇区、温度;纠错次数上涨是快坏的征兆 | §9.6.13 |
| 调度器和队列参数 | cat /sys/block/sda/queue/scheduler | 方括号里的是当前的调度器,none 表示不排序;同目录还有 nr_requests、read_ahead_kb | §9.4.4§9.9.1 |
| 单次 IO 有多慢(轻量) | ioping -c 10 . | 每秒一次小读,打印延迟;负载很轻,生产机上也能用 | §9.8.5 |
| 盘的上限 | fio --direct=1 …dd … oflag=direct | 同时开着 iostat 对照;一定要绕过 page cache,否则测的是内存 | §9.5.9§9.8.1 |
iostat、sar、pidstat 在 sysstat 包里,看自己的进程不要 root;带 -bpfcc 后缀的是 Debian bpfcc-tools 包里的 BCC 工具,biostacks.bt 在 bpftrace 包里,btrace、btt 在 blktrace 包里,这些都要 root。smartctl 在 smartmontools 包里,要 root。这些要 root 的工具本站没有在 WSL2 上实测,ioping、fio 也没有装。工具名的链接到本站的工具卡:iostat、sar、pidstat、biotop、iotop、biolatency、biosnoop、biostacks、perf、blktrace、smartctl、ioping、fio。
动手:在自己的机器上试
换 IO 大小、加并发,看 iostat -x 怎么变
用 dd 加 oflag=direct 绕过 page cache,让每次 write 都直接变成一次盘 IO,同时开着 iostat 看。先猜:块大小从 4 KB 换成 1 MB,每秒次数和每次的延迟各怎么变?四个 dd 一起写,%util 会不会变成原来的四倍?
# 找出你当前目录所在的盘:WSL2 上根目录一般是 sdX 里最大的那块,本站是 sdf
$ df . && lsblk
# 终端 1:每秒一行,-y 跳过开机以来的汇总
$ iostat -dxy sdf 1
# 终端 2:块大小 4K、64K、1M,各写一次再删掉(共 224 MB)
$ dd if=/dev/zero of=t.dat bs=4k count=8192 oflag=direct; rm t.dat
$ dd if=/dev/zero of=t.dat bs=64k count=1024 oflag=direct; rm t.dat
$ dd if=/dev/zero of=t.dat bs=1M count=128 oflag=direct; rm t.dat
# 一个写者和四个写者:每个在 32 MB 的文件上来回写四遍,持续三秒左右
$ w() { for k in 1 2 3 4; do dd if=/dev/zero of=$1 bs=4k count=8192 oflag=direct conv=notrunc 2>/dev/null; done; }
$ w s.dat; rm s.dat
$ for i in 1 2 3 4; do w p$i.dat & done; wait; rm p*.dat
本站在 WSL2 上看到的(iostat 里写的几列,各截了一行;4K 两行的吞吐是同一秒 iostat 的 wkB/s 换算的,64K 和 1M 不到一秒就写完了,吞吐用 dd 自己报的):
吞吐 w/s w_await wareq-sz aqu-sz %util 4K 1 个 46 MB/s 11172 0.07 4.00 0.74 72.0 4K 4 个 198 MB/s 48240 0.06 4.00 2.67 97.2 64K 1 个 526 MB/s - 0.08 63.97 - - 1M 1 个 3.6 GB/s - 0.19 1024.00 - -
- 块越大,每次 IO 越慢(0.07 ms 到 0.19 ms),吞吐却高了几十倍:吞吐等于 IOPS 乘大小。64K 和 1M 两次 dd 都不到一秒就写完了,那一秒里大半是空的,所以每秒的数没有意义,只看平均大小和平均延迟(§9.3.6)。
- 一个写者时,%util 约 70%、aqu-sz 约 0.7:只有一个 IO 在飞,盘「闲着」的那 30% 其实是 dd 在做系统调用。用书里的式子推服务时间:0.72 ÷ 11172 ≈ 0.064 ms,和 w_await 对得上,因为此时一次只有一个 IO(§9.3.1)。
- 四个写者时,每秒次数翻了四倍多,%util 只涨到 97%,w_await 不变。这块虚拟盘能同时处理多个 IO,100% 远不是它的上限;同一个式子这时会推出 0.02 ms 的「服务时间」,明显不对。aqu-sz 约等于 IOPS 乘 await(2.67 ≈ 48240 × 0.06 ms),这是 Little 定律(§2.6.5)。
- 1 MB 的写到了 3.6 GB/s,超过普通 SSD 的能力:多半是 WSL2 的虚拟盘背后还有 Windows 一侧的缓存,direct IO 只绕过了 Linux 自己的 page cache。所以这里的数只能看趋势,不能当真机的数。
- WSL2
- 本站实测过上面的命令,不需要 root(iostat 从 sysstat 包来)。WSL2 的盘是 Hyper-V 虚拟盘,
/sys/block/sdf/queue/rotational显示 1(按机械盘报),调度器是none;延迟和吞吐都受宿主缓存影响,数字不代表真机。别的程序在同一块盘上读写时,iostat 里会混进它们的 IO。 - 服务器
- 别在生产盘上跑写测试;要测就在空闲时、在单独的目录里,量控制在几百 MB。机械盘上把 4K 的那条换成随机写(用 fio 的
--rw=randwrite),会看到 IOPS 掉到一两百、%util 一下到 100%。 - ARM 板
- busybox 的 dd 支不支持
oflag=direct,看它的版本和裁剪,跑一下就知道;不支持时用下一个实验里的 C 程序。没有 iostat 时用下面的ds.sh从/proc/diskstats算。eMMC 和 SD 卡写得多了会磨损,测试量要小。
顺序读和随机读,以及没有 iostat 时怎么算
dd 只会顺序读写。下面这个程序用 O_DIRECT 打开文件,按顺序或随机偏移读,自己记时间,算出应用看到的平均延迟,可以和 iostat 的 r_await 对照,这就是最简单的分层延迟分析(§9.5.5)。
// rd.c:用 O_DIRECT 绕过 page cache,按顺序或随机偏移读一个文件,量每次读的平均延迟
// 用法:./rd 文件 seq|rand 块大小(字节) 次数
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
static double now(void) {
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return t.tv_sec + t.tv_nsec / 1e9;
}
int main(int argc, char **argv) {
if (argc != 5) { fprintf(stderr, "usage: %s file seq|rand bs count\n", argv[0]); return 1; }
int rnd = strcmp(argv[2], "rand") == 0;
size_t bs = strtoul(argv[3], 0, 0);
long n = atol(argv[4]);
int fd = open(argv[1], O_RDONLY | O_DIRECT); // O_DIRECT:缓冲区、偏移、长度都要按块对齐
if (fd < 0) { perror("open"); return 1; }
off_t blocks = lseek(fd, 0, SEEK_END) / bs;
void *buf;
if (posix_memalign(&buf, 4096, bs)) return 1;
srand(42);
double t0 = now();
for (long i = 0; i < n; i++) {
off_t b = rnd ? (off_t)(((unsigned long)rand() << 16 ^ rand()) % blocks) : i % blocks;
if (pread(fd, buf, bs, b * bs) != (ssize_t)bs) { perror("pread"); return 1; }
}
double t = now() - t0;
printf("%s bs=%zu: %.0f IOPS, %.1f MB/s, avg %.1f us\n", argv[2], bs, n / t, n * bs / t / 1e6, t / n * 1e6);
return 0;
}
$ gcc -O2 -Wall rd.c -o rd # 准备一个 128 MB 的文件(内容随机),iostat 照样在另一个终端开着 $ dd if=/dev/urandom of=big.dat bs=1M count=128; sync $ ./rd big.dat seq 4096 30000 $ ./rd big.dat rand 4096 30000 $ ./rd big.dat seq 1048576 1500 $ rm big.dat
本站在 WSL2 上看到的(右边是同一时段 iostat 的 r_await):
seq bs=4096: 8425 IOPS, 34.5 MB/s, avg 118.7 us r_await 0.10
rand bs=4096: 5501 IOPS, 22.5 MB/s, avg 181.8 us r_await 0.15
seq bs=1048576: 2248 IOPS, 2357.7 MB/s, avg 444.7 us r_await 0.41
- 应用量到的 119 µs 比 r_await 的 100 µs 多出约 20 µs:那是系统调用和文件系统的开销,不在块层里。两者接近,说明延迟主要在盘这一侧(§9.5.5)。
- 随机读比顺序读慢了一半左右,但远没有机械盘上几十倍的差距:这块虚拟盘背后是 SSD 加宿主缓存。真 SSD 上随机读和顺序读通常差不多(§9.3.4)。
- 程序一次只有一个读在飞,所以 IOPS 约等于 1 秒除以平均延迟。想要更高的 IOPS,得同时发多个 IO(多线程或异步 IO),这正是 SSD 需要的。
板子上没有 iostat 时,iostat 的几列都能从 /proc/diskstats 两次读数的差算出来。下面这个脚本只用 sh 和 awk,busybox 里一般都有:
#!/bin/sh
# ds.sh:没有 iostat 时,从 /proc/diskstats 算一块盘这一秒的 r/s、w/s、await、aqu-sz、%util
# 用法:sh ds.sh sda
s() { awk -v d="$1" '$3 == d { print $4, $8, $7, $11, $13, $14 }' /proc/diskstats; }
# 六个数依次是:读完成次数、写完成次数、读耗时、写耗时、忙的时间、加权耗时(后四个单位是毫秒)
a=$(s "$1"); sleep 1; b=$(s "$1")
echo "$a $b" | awk '{ n = ($7 - $1) + ($8 - $2)
printf "r/s %d w/s %d await %.2f ms aqu-sz %.2f %%util %.1f\n",
$7 - $1, $8 - $2, n ? (($9 - $3) + ($10 - $4)) / n : 0, ($12 - $6) / 1000, ($11 - $5) / 10 }'
$ sh ds.sh sdf
ds.sh: r/s 0 w/s 10345 await 0.06 ms aqu-sz 0.67 %util 66.4 iostat: w/s 10312 w_await 0.07 aqu-sz 0.67 %util 66.0
两边是同一秒里各自取的样,差一点是正常的。%util 就是「忙的时间」这个计数器每秒涨了多少毫秒;aqu-sz 是「加权耗时」每秒涨了多少毫秒再除以 1000。看清了这两个计数器怎么来的,就明白 %util 为什么说不出盘还剩多少能力。
- WSL2
- 本站实测过 rd 和 ds.sh,不需要 root;ds.sh 和 iostat 的结果在同一秒里对得上。
- 服务器
- rd 可以直接对一个大文件跑(不要对裸设备做写测试)。机械盘上随机 4 KB 读的平均延迟会到几毫秒,和顺序读差出一两个数量级;顺带用
iostat -x看rrqm/s:顺序读常有合并,随机读没有。 - ARM 板
- rd 用交叉编译器编一下就能跑,O_DIRECT 要文件系统支持(ext4 支持;不支持的文件系统上会报错)。eMMC 在
/proc/diskstats里叫mmcblk0,SD 卡常是mmcblk1,看你的板子。
动手:模拟器
先猜:一块 NVMe SSD,一个线程一次读 4 KB,%util 是多少?这时它用了自己多少能力?把并发从 1 加到 32,IOPS 涨几倍、%util 又涨几倍?再换成机械盘,把随机比例从 0 拖到 10%,IOPS 会掉多少?
IO 大小、随机与顺序
一个简化的盘:每条流发一个 IO,等它完成,花 10 µs 处理,再发下一个。选盘、IO 大小、随机比例和同时在飞的 IO 数,看 IOPS、吞吐、延迟、iostat 算出的 %util,和盘真正用了多少能力。时间条画的是前几条流的 IO:橙色是排队,蓝色是在盘上服务。
时间条
排队在盘上服务。灰条是任一 IO 在盘上的时间,%util 就是它占的比例。
盘内的并行单元
- NVMe、4 KB 随机、并发 1:%util 约 90%,「能力用了」不到 3%。连点「并发加倍」到 32:IOPS 涨到约 32 倍,%util 只是从 90% 到 100%。这就是 iostat 的 %util 在 SSD 上说不出「还剩多少」的原因。
- 换成机械盘,4 KB,随机比例 0%:每秒上万次;拖到 10%:每次 IO 平均多出 0.8 ms 的寻道和等待,IOPS 掉到一千出头。再把并发加到 8:IOPS 不涨,await 涨到约 8 倍,aqu-sz 约等于 8,这是真饱和。
- 选 SATA SSD、1 MB 顺序:并发 1 时「带宽用了」就已经九成多,加到 2 就撞上 530 MB/s 的接口上限。大 IO 先撞带宽,小 IO 先撞次数,所以 IOPS 和吞吐要一起看。
成书之后
pidstat 的 iodelay、iotop 的 IO% 默认没有数了
- 书里
- 用
pidstat -d的iodelay看进程被磁盘 IO 阻塞了多久,并说 iotop 需要内核开着几项任务统计的配置(§9.6.4、§9.6.8)。 - 今天
- Linux 5.14(2021 年)起,任务的延迟统计(delay accounting)默认关闭,iodelay 会一直是 0,iotop 的 IO 一列显示不可用。打开的办法是启动参数加
delayacct,或者运行时sysctl kernel.task_delayacct=1,后者只对打开之后启动的进程有效。本站的 WSL2 上/proc/sys/kernel/task_delayacct是 0,一个正在直接写盘的 dd,iodelay 读出来也是 0。
cgroup 的磁盘限额换成了 v2 的 io 控制器
- 书里
- 按进程组限磁盘 IO 用 cgroup 的 blkio 子系统,可以按比例分配,也可以设 IOPS 或吞吐的上限(§9.9.1)。
- 今天
- blkio 是 cgroup v1 的名字。用 systemd 的发行版已经默认 cgroup v2(Debian 从 11 开始),对应的是 io 控制器:
io.max设读写的字节和次数上限,io.weight按比例分(要内核开了相应的支持),io.latency保护延迟目标,io.pressure是这个组自己的 PSI。本站的 WSL2 就是 cgroup v2,子组里能看到io.max、io.latency、io.pressure。容器里 IO 慢,先看这个组的io.max和io.pressure。
Optane 停产了
- 书里
- 介绍 Intel 和 Micron 的 3D XPoint(品牌 Optane),预期它会以介于内存和闪存之间的性价比用到更多场合(§9.4.1)。
- 今天
- Intel 在 2022 年 7 月的季度财报中宣布逐步结束 Optane 业务,不再开发后续产品。书里关于持久内存的性能对比仍可当背景读,但选型时已经不是一个选项。
分节地图
这一章按书的固定套路走:背景(§9.1–9.3)、架构(§9.4)、方法(§9.5)、工具(§9.6)、可视化、实验和调优(§9.7–9.9)。上手只需要概念里讲时间、IO 大小与随机、利用率、饱和度、iowait 的几节,方法里的 USE、负载特征和延迟分析,工具里的 iostat、PSI、pidstat、biolatency、biosnoop。机械盘和 SSD 的内部、RAID、调度器,用到时再查。
- Disk I/O can cause significant…约 300 词开篇磁盘为什么值得查,SSD 也会出问题;学习目标当目录扫一眼。
- Terminology约 200 词术语吞吐和带宽、IO 延迟、延迟异常值怎么区分。
- Models约 100 词模型三个简单模型的引子。
- Simple Disk约 100 词简单磁盘盘自己带队列,可以按排队论分析。
- Caching Disk约 200 词带缓存的磁盘盘内缓存让延迟分成命中、未命中两种;回写和直写。
- Controller约 100 词控制器两条总线、控制器、盘,任何一处都可能是上限。
- Concepts约 100 词概念一组概念的引子。
- Measuring Time约 700 词量时间等待、服务、请求时间各从哪儿量到哪儿;用 IOPS 和利用率推服务时间的局限。
- Time Scales约 300 词时间尺度从几十微秒到上千毫秒;多少毫秒算慢,看环境。
- Caching约 100 词缓存从应用到盘有好几层缓存,最好的 IO 是不发生。
- Random vs. Sequential I/O约 300 词随机与顺序机械盘怕随机,闪存基本不在乎;操作系统看到的偏移不等于物理位置。
- Read/Write Ratio约 100 词读写比读多加缓存,写多加盘。
- I/O Size约 200 词IO 大小大 IO 吞吐高、单次慢;闪存盘对读写大小各有偏好。
- IOPS Are Not Equal约 200 词IOPS 不能直接比IOPS 要连同大小、随机与否、读写一起说。
- Non-Data-Transfer Disk Commands约 100 词不传数据的命令缓存刷盘、TRIM 也会占着盘。
- Utilization约 500 词利用率60% 就可能开始排队;虚拟盘的利用率为什么和底下的盘对不上。
- Saturation约 100 词饱和度队列长度;利用率不到 100% 也可能有排队。
- I/O Wait约 300 词IO 等待iowait 为什么会误导,更可靠的是线程被 IO 阻塞的时间。
- Synchronous vs. Asynchronous约 200 词同步与异步盘慢不一定让应用慢:回写、预读、IO 线程。
- Disk vs. Application I/O约 100 词磁盘 IO 与应用 IO盘上的 IO 和应用发的 IO 数量、大小对不上的几种原因。
- Architecture约 100 词架构容量规划时读,排查时回来核对。
- Disk Types约 2800 词磁盘类型机械盘的寻道和旋转、扇区、振动;SSD 的闪存、FTL、写放大、寿命;持久内存。
- Interfaces约 800 词接口SCSI、SAS、SATA、FC、NVMe;NVMe 的多队列。带宽数字要查当前规格。
- Storage Types约 1000 词存储类型直连盘、RAID 各级别的性能、存储阵列、NAS。
- Operating System Disk I/O Stack约 800 词操作系统的磁盘 IO 栈块层的合并、IO 调度器、blk-mq。改调度器前读。
- Methodology约 100 词方法方法总表和作者推荐的顺序。
- Tools Method约 200 词工具法挨个跑工具,并给了几个参考阈值。
- USE Method约 400 词USE 方法对盘和控制器分别查利用率、饱和度、错误;控制器的利用率按吞吐算。
- Performance Monitoring约 200 词性能监控长期盯利用率和响应时间,要按盘看。
- Workload Characterization约 600 词负载特征五个基本属性怎么合起来描述负载;后面两张值得照着问的清单。
- Latency Analysis约 300 词延迟分析在栈的各层量延迟,找出慢在哪一层。
- Static Performance Tuning约 300 词静态性能调优一张配置核对清单:盘、控制器、RAID、固件、驱动。
- Cache Tuning约 100 词缓存调优指向第 2 章的缓存调优。
- Resource Controls约 100 词资源控制按进程限 IOPS 或吞吐。
- Micro-Benchmarking约 400 词微基准测试测盘和控制器上限时,IO 大小、偏移、并发怎么配。
- Scaling约 300 词扩展按目标负载算要几块盘、几个控制器;别按 100% 算。
- Observability Tools约 100 词观测工具工具总表:传统工具、跟踪工具、控制器工具。
- iostat约 1200 词iostat-x 的每一列,先看哪几列;为什么要把读写分开看。
- sar约 200 词sar能看历史的磁盘统计;svctm 为什么被删了。
- PSI约 100 词PSIIO 压力的趋势,适合做告警。
- pidstat约 200 词pidstat按进程看读写量和 iodelay。
- perf约 400 词perf记录块层跟踪点和调用栈,找发 IO 的代码。
- biolatency约 300 词biolatency延迟直方图,找双峰;按 IO 标志拆开。
- biosnoop约 500 词biosnoop每个 IO 一行;从开始时间看出排队,按延迟排序找异常值。
- iotop, biotop约 400 词iotop 与 biotop按进程排序的磁盘 top;iotop 的计数要先验证。
- biostacks约 100 词biostacks带调用栈的块 IO 延迟,找不是读写引起的 IO。
- blktrace约 500 词blktrace每个 IO 的各个阶段;btt 统计各阶段耗时。
- bpftrace约 600 词bpftrace现场写单行命令看 IO 大小、类型、错误、延迟。
- MegaCli约 200 词MegaCli只针对 LSI 控制器;换成你的控制器厂商的工具。
- smartctl约 100 词smartctl盘自己报的健康数据,用来预测故障。
- SCSI Logging约 100 词SCSI 日志调试 SCSI 错误时才用,打开后会刷满系统日志。
- Other Tools约 100 词其他工具BPF 书里的更多磁盘工具和 /proc/diskstats。
- Visualizations约 100 词可视化几种图的引子。
- Line Graphs约 100 词折线图平均值会藏住双峰和单块盘的异常。
- Latency Scatter Plots约 200 词延迟散点图每个 IO 一个点,看异常值和读写的关系。
- Latency Heat Maps约 300 词延迟热图事件多到散点图看不清时用热图。
- Offset Heat Maps约 100 词偏移热图看访问位置的老方法,SSD 上意义不大。
- Utilization Heat Maps约 100 词利用率热图成规模的机器才用得上,单机排查用不到。
- Experimentation约 100 词实验压测时让 iostat 一直开着对照。
- Ad Hoc约 200 词临时测试用 dd 测顺序读写;direct 标志绕过缓存。
- Custom Load Generators约 100 词自己写负载C 程序加 O_DIRECT 造自己想要的负载。
- Micro-Benchmark Tools约 100 词微基准工具hdparm 测缓存读和盘读。
- Random Read Example约 100 词随机读的例子并发一级级加,看队列和延迟一起涨。
- ioping约 100 词ioping像 ping 一样测单次 IO 延迟,负载很轻,生产上也能用。
- fio约 100 词fio指向第 8 章;用 direct 模式测盘。
- blkreplay约 100 词blkreplay回放 blktrace 记录的负载,专门场景才用。
- Tuning约 100 词调优默认值通常够用,先去掉不必要的 IO。
- Operating System Tunables约 300 词操作系统的可调项ionice、cgroup 的 IO 限额、调度器和预读参数。
- Disk Device Tunables约 100 词磁盘设备的可调项hdparm 的设置,多是电源管理,有的选项会丢数据。
- Disk Controller Tunables约 100 词磁盘控制器的可调项随厂商和型号而定,看你的控制器文档。
- Exercises约 200 词习题习题;动手部分本页已经给了替代。
词与短语
只收先读和用到再查两档的节。斜体那一行是原句里连续的几个词,在电子书里搜它就能找到那一句。
§0 开篇
§9.2.1 Simple Disk
§9.2.2 Caching Disk
§9.3.2 Time Scales
§9.3.7 IOPS Are Not Equal
§9.3.9 Utilization
§9.3.10 Saturation
§9.3.11 I/O Wait
§9.4.1 Disk Types
§9.4.4 Operating System Disk I/O Stack
§9.5.2 USE Method
§9.5.4 Workload Characterization
§9.5.5 Latency Analysis
§9.5.10 Scaling
§9.6.1 iostat
§9.6.6 biolatency
§9.6.7 biosnoop
§9.6.8 iotop, biotop
§9.9 Tuning
诊断题:换成你来查
场景和输出都是本站编的。先自己判断,再点选项看解释。
场景:一台服务器跑你的 C++ 键值服务,数据在一块 NVMe SSD 上,读路径是一个线程同步 pread。高峰时你截了一秒的 iostat:
Device r/s rareq-sz r_await aqu-sz %util nvme0n1 11800 4.0 0.08 0.96 99.8
aqu-sz 约等于 r/s 乘 r_await(11800 × 0.08 ms ≈ 0.94),说明盘上基本只有一个读:瓶颈是这一个线程串行地等,不是盘。%util 99.8% 只说明这一秒里一直有一个 IO 挂着。下一步:读路径改成多线程或异步 IO,再看 IOPS 涨不涨、r_await 动不动;也可以在空闲时用 fio 测一下这块盘在高并发下的上限当基准。0.08 ms 对 NVMe 本来就是正常的读延迟。
场景:一台老归档机,数据在一块 7200 转机械盘上,一个查询服务按键随机读 4 KB 记录。用户说查询越来越慢:
Device r/s rkB/s rrqm/s rareq-sz r_await aqu-sz %util sdb 160 640 0.00 4.0 38.5 6.2 100.0
平均大小 4 KB、没有合并,是随机小读;每次要寻道加等旋转,一块 7200 转的盘每秒也就一两百次。aqu-sz 6.2 说明盘上加队列里平均有六个左右,await 38 ms 里大半是排队(6.2 ÷ 160 ≈ 39 ms,Little 定律)。吞吐低正是因为 IO 小,IOPS 不能拿吞吐来衡量。预读对随机读没用,只会多读用不上的数据。盘坏也会慢,值得顺手看一眼 smartctl,但这组数和随机负载完全吻合。
场景:8 核服务器。运维在凌晨 2 点加了一个日志压缩任务。监控上 iowait 从 25% 掉到 3%,同事说「压缩把盘的问题解决了」,可服务读请求的延迟没变:
time %usr %iowait %idle 01:59 6.0 25.1 68.2 02:01 71.4 3.2 24.9
iowait 是 CPU 空闲时间的一部分。压缩任务把 CPU 占到七成,CPU 不再空闲,iowait 自然变小,而原来等盘的线程照样在等。这正是书里说 iowait 容易误导的情形。要量应用到底被盘挡了多久,看 /proc/pressure/io、vmstat 的 b 列,或者用 biolatency 看盘的延迟分布。
场景:一台日志服务器,SATA SSD。iostat 里 r_await 平均 1.2 ms,可应用每隔几秒就有一批读超过 30 ms。你跑了十秒 biolatency -Fm:
flags = Read msecs : count 0 -> 1 : 9120 2 -> 3 : 210 32 -> 63 : 41 flags = Write 0 -> 1 : 380 2 -> 3 : 2950 4 -> 7 : 3300
直方图上读有一小撮落在 32–63 ms,写的量又大又慢,平均值把这些都抹平了。书里的散点图例子就是写突发之后出现读的异常值,作者推测读排在了写后面(§9.7.2)。下一步用 biosnoop-bpfcc -Q 存一段,按延迟排序,看慢读之前的几十毫秒里发生了什么。确认是写突发,再考虑让脏页更早、更平稳地刷出去,或者换能限制写饿死读的调度器(§9.4.4);换调度器是可以试的方向,但「一定」说过头了。
场景:一块 ARM 板,日志写在 eMMC 上。程序每条日志(约 200 字节)write 一次再 fsync 一次。用 ds.sh 看了一秒:
mmcblk0: w/s 620 await 3.10 ms aqu-sz 1.9 %util 97.0
每次 fsync 至少落一次 4 KB 的数据写,加上文件系统日志,几百字节的日志变成了每秒六百多次小写。闪存怕的正是小块写(写放大、擦除),IOPS 先到顶(§9.4.1)。攒批是从源头减少 IO 次数,也能延长 eMMC 的寿命;代价是断电时可能丢最后一批,要按业务定间隔。O_DIRECT 不减少次数,还要求对齐;调度器和预读都和这里无关。
值得停下来的句子
The best disk I/O performance is none at all.— §9.3.3。本站译:最好的磁盘 IO 性能,是根本不发生 IO。
这句话放在缓存一节的开头,却是整章的底色。前面的工具能告诉你盘忙不忙、慢在哪,但最大的收益常常来自让 IO 消失:命中缓存、攒批写、去掉不必要的 fsync、别让日志和数据抢同一块盘。负载特征那一节也说,看清施加的负载,可能找到可以整个砍掉的工作(§9.5.4)。查到盘是瓶颈之后,先问「这些 IO 必须发吗」,再问「怎么让盘更快」。
bear in mind that it is only a measure of busyness (non-idle time) and may mean little for virtual devices backed by multiple disks.— §9.6.1。本站译:要记住,它(%util)只衡量忙碌,也就是非空闲的时间;对背后有多块盘的虚拟设备,它可能说明不了什么。
书里这句说的是虚拟设备,道理对 SSD 一样成立:只要设备能同时处理多个 IO,「一直有 IO 在飞」和「接不下更多了」就是两回事。监控面板上最显眼的往往就是 %util,告警阈值也常设在它上面。读懂这句话,就不会因为一块 NVMe 的 %util 到了 100% 而急着换盘,而会去看 await 有没有涨、队列有没有堆起来。
带走的几条
一句话:先确认应用在等盘,再按盘看延迟和排队,最后用延迟分布和逐个 IO 找到异常值和发起者。
iostat -sxz 1。await 是每次 IO 的总时间,aqu-sz 是队列长度;%util 放在最后看。
§9.6.1%util 只算有 IO 在飞的时间。能并行的设备,看 await 是否随负载涨、aqu-sz 和压测上限比。
§9.3.9iostat -dx 1。写多被回写缓存吸收,r_await 通常更直接关系到应用的延迟。
§9.6.1吞吐 = IOPS × 大小。rareq-sz 小、合并为零是随机小 IO;机械盘每秒一两百次就到顶。
§9.3.7CPU 一忙它就降。看 /proc/pressure/io 和 vmstat 的 b 列,量线程被 IO 挡住的时间。
§9.3.11biolatency 看分布找双峰,biosnoop 逐个找慢 IO,看它前面是不是刚来过一批写。
§9.6.6dd 加 oflag=direct 或 fio --direct=1,同时开着 iostat 核对;虚拟机里的数不代表真机。
§9.8.1