开始学习

Stage 02 · 进阶 · 第 2 / 7

性能分析方法论

USE 方法、负载特征刻画、延迟分析与"没有基线的调优都是玄学"。

40 分钟|全程第 12 / 38

半夜收到告警"存储慢了",你登上机器会先敲什么?大多数人的答案是 top,然后 iostat,然后……开始凭感觉改参数:把调度器换一下、把 read_ahead_kb 调大、重启一下服务。运气好问题消失了,但你并不知道是哪一步起了作用;运气不好,问题还在,还多了几个没人记得改过的参数。

这一课讲的是"怎么想"而不是"用什么工具":一套能反复使用的性能分析方法。学完你能:认出并避开常见的反方法;用几个问题把模糊的"慢了"变成可分析的问题陈述;用 USE 方法系统地检查磁盘、控制器和文件系统;用负载特征刻画和延迟分析逐层定位瓶颈;理解为什么没有基线的调优都是玄学;在 60 秒内对一台陌生机器做出第一轮判断。本课主要取材于《Systems Performance》第 2 章(方法论)、第 1 章 1.10 节和第 9 章 9.5 节(磁盘分析方法)。

反方法:你可能每天都在用

《Systems Performance》第 2 章开篇先列了几种"反方法(Anti-Methods)"。它们之所以流行,是因为它们不需要思考。

路灯反方法

一个醉汉在路灯下找钥匙,警察问他钥匙丢在哪了,他说"丢在马路对面,但这里有光"。**路灯反方法(Streetlight Anti-Method)**就是只用自己熟悉的工具、看自己熟悉的指标——因为 top 用得熟,所以永远从 top 开始,也往往止步于 top。问题要是恰好在你熟悉的地方,你能找到;不在,你就会得出"系统没问题"的结论。

随机调参反方法

随机调参反方法(Random Change Anti-Method):猜一个可能的原因,改个参数,看看有没有变好。它的问题不只是慢:

  • 改了之后"好像快了",可能只是负载恰好变轻了;
  • 一个参数在测试时有用,在另一种负载下可能是负优化;
  • 几轮下来,系统上堆满了没有记录、没有理由的改动,下一个接手的人会恨你。

甩锅反方法

甩锅反方法(Blame-Someone-Else Anti-Method):找一个不归自己管的组件,假设问题在那里——"肯定是网络的问题""存储那边看看?"。书里给了一个识别它的信号:对方让你提供截图,却说不出自己怀疑什么、依据是什么。

临时清单

还有一种半吊子方法:临时检查清单(Ad Hoc Checklist),比如"iostat -xr_await,超过 10 ms 就是盘慢"。清单能在短时间内覆盖常见问题,值得保留,但它只能发现清单上有的问题,而且需要随着系统变化不断更新,不能代替分析。

问题陈述:先把"慢"说清楚

"存储慢了"不是一个可以分析的问题。《Systems Performance》第 2 章给出的问题陈述法(Problem Statement Method),是在动手前问清楚这几件事:

问题为什么要问
你为什么认为有性能问题?是监控告警、用户投诉,还是某人"感觉"?证据是什么?
这个系统以前表现好吗?如果从来没好过,这是容量规划或架构问题,不是故障
最近改过什么?软件、硬件、负载?大部分性能退化都和变更相关
问题能用延迟或运行时间描述吗?"p99 从 2 ms 涨到 40 ms"比"慢"有用一百倍
影响其他人或其他应用吗?只影响一个应用,多半在应用侧;影响全部,看共享资源
环境是什么?硬件、软件版本、配置、拓扑、虚拟化层

光是问完这几个问题,相当比例的"性能问题"就已经有答案了——"昨天上线了新版本""上周把副本数从 2 改成了 3""那台机器的 RAID 卡电池坏了,写缓存被关掉了"。

有了问题陈述,就可以用**科学方法(Scientific Method)**推进:提出问题 → 假设 → 预测 → 测试(观测或实验)→ 分析结果。比如:假设"p99 升高是因为后台 scrub 抢占了磁盘",预测"暂停 scrub 后 p99 应回落",然后去验证。假设被否定也是进展,它帮你排除了一个方向。

USE 方法

**USE 方法(Utilization, Saturation, Errors)**是 Brendan Gregg 提出的、最适合在排查初期使用的系统化检查方法:

对每一种资源,检查它的利用率、饱和度和错误。

  • 利用率(Utilization):资源忙碌的时间占比,或已用能力占比;
  • 饱和度(Saturation):资源无法及时处理、排队等待的工作量;
  • 错误(Errors):错误事件的数量。

它的价值在于"迭代资源"而不是"迭代工具":先列出系统里所有资源,再逐一问三个问题。这样不会因为你不熟悉某个工具就漏掉某个资源——正好是路灯反方法的反面。书里特别强调先查错误:错误通常最容易解释、最快排除,一个在不断重试的设备会让利用率和饱和度指标都失真。

存储相关的 USE 检查清单

结合《Systems Performance》第 9 章 9.5.2 节,以下是存储路径上各资源的 USE 清单(工具细节在后面几课展开):

资源类型指标与工具
磁盘设备利用率iostat -xz 1%util(并行设备上要谨慎解读);sar -d
磁盘设备饱和度iostataqu-sz 大于设备有效并行度、await 持续升高;/proc/pressure/io
磁盘设备错误dmesg -T 中的 I/O error、超时、重置;smartctl -anvme smart-log/sys/block/<dev>/device/ioerr_cnt(SCSI)
存储控制器(HBA/RAID 卡)利用率把它下挂所有盘的吞吐和 IOPS 加起来,和控制器/链路标称上限比较
存储控制器饱和度所有盘都不忙但延迟高;吞吐总是平在同一个数值
存储控制器错误厂商工具(storclissacli 等)、dmesg、BBU/电容状态
传输链路(PCIe/SAS/网络)利用率PCIe 协商速率与宽度 lspci -vvv;网络口 sar -n DEV 1
传输链路错误PCIe AER 日志、ethtool -S 的错误计数、sar -n EDEV 1
文件系统利用率容量 df -h、inode df -i;页缓存占用 free -w
文件系统饱和度内存回收压力(vmstatsi/sosar -Bpgscand/s)、脏页阻塞写
文件系统错误dmesg 中的 EXT4-fs/XFS 错误、只读重挂载
CPU利用率/饱和度mpstat -P ALL 1(尤其是处理中断的核);vmstatr

USE 方法的局限也要清楚:它能找到"哪个资源有问题",但对软件层面的问题(锁竞争、应用逻辑串行化、一次请求产生过多 I/O)力不从心。这时就要换到下面的方法。

负载特征刻画

**负载特征刻画(Workload Characterization)**关注的是"施加了什么负载",而不是"系统表现如何"。书中给出四个问题:

问题存储场景的具体化
谁在产生负载?哪个进程、哪个容器、哪个客户端 IP、哪个租户
为什么产生负载?业务请求?备份?压缩?日志轮转?一个跑飞的定时任务?
负载是什么样的?IOPS、吞吐、I/O 大小、读写比、随机/顺序、同步/异步
负载随时间怎么变化?是否有周期、突发,与业务高峰是否吻合

这是一种非常划算的方法,原因写在书里:最大的性能收益往往来自消除不必要的工作。你花三天把某块盘的调度器调到最优,收益可能是 10%;而发现某个服务每分钟把 2 GB 日志全量重写一遍、把它停掉,收益是 100%。

一个典型的刻画过程,用到的命令都会在下一课详细讲:

console
$ iostat -xz 1 5                  # 负载是什么样:IOPS、吞吐、I/O 大小、读写比
$ sudo pidstat -d 1 5             # 谁在产生负载:按进程的读写量
$ sudo timeout 10 biosnoop-bpfcc  # 负载的细节:每个 I/O 的进程、扇区、大小、延迟
$ sar -d -f /var/log/sysstat/sa$(date -d yesterday +%d)   # 负载随时间的变化:和昨天比

刻画的结果应该能写成一段话,就像上一课给出的那个范例:几 IOPS、多少 MB/s、读写比例、I/O 大小、有无突发。

延迟分析:逐层下钻

**延迟分析(Latency Analysis)**是把一次操作的总时间拆开,看时间花在了哪一层,然后对占比最大的那一层继续拆,直到找到根因。对存储 I/O,自然的分层就是 I/O 栈

text
应用请求                     ← 应用自己的日志/APM:一次请求 200 ms
  └─ 系统调用 read/write     ← strace -T / syscount:其中 I/O 系统调用 180 ms
      └─ VFS / 文件系统       ← ext4slower / xfsdist:文件系统层 175 ms
          └─ 块设备层          ← biolatency -Q:含 OS 队列 170 ms
              └─ 设备          ← biolatency(不带 -Q)/ blktrace D2C:设备 5 ms

在这个例子里,时间几乎全部耗在块层"进入设备之前":OS 队列里 165 ms、设备本身只花了 5 ms。结论很明确——不是盘慢,是请求在队列里积压(可能被某个大 I/O 流抢占,或 nr_requests 太小、调度器策略不当),下一步应该去看是谁把队列塞满了。

《Systems Performance》第 9 章图 9.10 讲了一个非常好的判断技巧:比较同一个慢请求在各层的延迟

  • 某个异常值在每一层看到的延迟都差不多,说明它起源于最底层的磁盘;
  • 某个异常值在文件系统层很大、到块层就消失了,说明它起源于文件系统(比如等锁、等日志提交),和磁盘无关。

基线:没有基线的调优都是玄学

"await 是 3 ms,正常吗?"——没有人能回答这个问题,除非你知道它平时是多少。

**基线(Baseline)**是系统在正常状态下的性能数据。有了它,你才能说"比平时高了 5 倍"而不是"好像有点高"。《Systems Performance》第 2 章给出的建议包括:

  • 定期(比如每天)采集基线统计,而不只是出事时才采;
  • 在任何变更(升级、调参、扩容)前后各采一次;
  • Netflix 的做法是在监控面板上把当前曲线和上周同一时刻叠在一起,一眼看出偏差。

对存储来说,最低限度的基线是:

基线怎么获得
设备的能力上限上线前用 fio 做一轮标准测试(见基准测试),存档结果
日常负载曲线开启 sysstat 历史采集,或 Prometheus node_exporter 的磁盘指标
延迟分布定期跑一次 biolatency-bpfcc 60 1 存档直方图
配置快照调度器、队列参数、sysctl、挂载选项,变更前后 diff

Ubuntu 24.04 上 sysstat 默认不开历史采集,打开它只需要两步:

console
$ sudo sed -i 's/^ENABLED="false"/ENABLED="true"/' /etc/default/sysstat
$ sudo systemctl enable --now sysstat
$ ls /var/log/sysstat/          # 之后每 10 分钟采样一次,按天存为 saDD
sa24
$ sar -d -f /var/log/sysstat/sa24 | head

60 秒检查清单

当你登上一台陌生的、"出了性能问题"的 Linux 机器,《Systems Performance》第 1 章 1.10.1 节给出了 Netflix 性能团队用的 60 秒清单。它本质上是一份覆盖 USE 主要资源的临时清单,用的全是标准工具:

#命令看什么
1uptime负载平均值的趋势:1/5/15 分钟,负载是在上升还是回落
2dmesg -T | tail最近的内核错误:I/O error、OOM、TCP 丢包、硬件告警
3vmstat -SM 1r(CPU 排队)、b(阻塞在 I/O)、freesi/sowa
4mpstat -P ALL 1各 CPU 是否均衡,有没有单核被中断或单线程打满
5pidstat 1按进程的 CPU 使用,找出是谁
6iostat -sxz 1磁盘 IOPS、吞吐、awaitaqu-sz%util
7free -m内存和页缓存,available 是否充足
8sar -n DEV 1网卡吞吐,是否接近线速(网络存储尤其要看)
9sar -n TCP,ETCP 1TCP 连接速率与重传
10top最后再整体看一眼

在一台存储节点上跑前几条,典型的"磁盘有问题"会长这样:

console
$ vmstat -SM 1 3
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2 14      0    812     90  28641    0    0 52340  8120 9812 15520  4  3 41 52  0
 1 15      0    806     90  28650    0    0 51880  7960 9655 15102  3  3 42 52  0
 1 13      0    799     90  28655    0    0 52910  8340 9921 15644  4  3 40 53  0

$ iostat -sxyz 1 1
Device             tps      kB/s    rqm/s   await  aqu-sz  areq-sz  %util
sda             325.00  60432.00     0.00   42.61   13.85   185.95  100.00

b 列有十几个进程阻塞在 I/O 上,wa(I/O 等待)占了一半 CPU 时间,sdaawait 42 ms、aqu-sz 接近 14,areq-sz 约 186 KB——一块机械盘被大块 I/O 压满、严重排队。60 秒内,你已经从"系统慢了"推进到"sda 饱和了",接下来用负载特征刻画去找是谁、为什么。

把方法串起来

《Systems Performance》第 9 章 9.5 节建议磁盘性能分析按这个顺序组合各种方法:

text
USE 方法 ──▶ 性能监控 ──▶ 负载特征刻画 ──▶ 延迟分析 ──▶ 微基准测试 ──▶ 静态调优 ──▶ 事件追踪
(哪个资源)  (和基线比)  (谁、干了什么)   (时间花在哪层) (设备能力上限)  (检查配置)   (单个 I/O 细节)

日常排查不必每一步都做,但心里要有这个顺序:先宽后窄、先便宜后昂贵。事件追踪(blktrace、BPF 逐事件跟踪)功能最强,但开销和数据量也最大,放在最后。

最后,书中还列了一组"性能箴言",按收益从高到低排列,适合在动手调优前念一遍:

  1. 不做(Don't do it)——消除不必要的工作;
  2. 做一次(Do it, but don't do it again)——缓存;
  3. 少做(Do it less)——降低频率,比如降低刷新或轮询频率;
  4. 晚点做(Do it later)——回写缓存;
  5. 在别人不注意时做(Do it when they're not looking)——把 scrub、compaction 安排在低峰;
  6. 并发地做(Do it concurrently)——提高队列深度;
  7. 做得更便宜(Do it more cheaply)——换更快的硬件。

注意"换更快的硬件"排在最后。

这不是说硬件不重要,而是说它最贵、见效最不确定:如果瓶颈在串行化的 fsync 或一个跑飞的任务上,换再快的盘也只是把钱花在了没有排队的地方。

动手练习

  1. 写一份问题陈述。 回忆一次你遇到过的"系统慢了"(或者编一个),按问题陈述法的六个问题写出答案,再根据答案提出至少两个可验证的假设和对应的验证方法。
  2. 跑一遍 60 秒清单。 在实验机上用 fio --name=bg --filename=/dev/vdb --readonly --direct=1 --rw=randread --bs=4k --iodepth=32 --runtime=300 --time_based & 在后台制造负载,然后按清单逐条执行,记录每条命令里哪些数字反映了这个负载。
  3. 做一次 USE 检查。 为你的实验机列出存储路径上的所有资源(盘、控制器/虚拟磁盘、文件系统、CPU),按本课的清单逐项填写 U/S/E 的当前值和使用的命令。
  4. 建立基线。 开启 sysstat 历史采集,一天后用 sar -d -f 查看历史数据;同时把当前的调度器、nr_requestsread_ahead_kbsysctl vm.dirty_* 和挂载选项保存到一个文件,作为日后的配置基线。

自测

什么是路灯反方法?USE 方法是如何避免它的?

路灯反方法是只用自己熟悉的工具、看熟悉的指标,而不管问题实际在哪里。USE 方法先列出系统的所有资源,再对每个资源检查利用率、饱和度和错误,是"迭代资源"而不是"迭代工具",因此不会因为不熟悉某个工具就漏掉某个资源。

12 块 NVMe 每块的 %util 都只有 30%,但总吞吐始终卡在约 14 GB/s 上不去。按 USE 方法应该怀疑什么?如何验证?

应怀疑盘之上的共享资源:存储控制器、PCIe 交换芯片或上游链路的利用率已饱和。验证方法:把所有盘的吞吐相加,与控制器/链路的标称带宽比较;用 lspci -vvv 检查 LnkSta 的速率和宽度是否降级(downgraded);检查盘和控制器所在的 NUMA 节点与 CPU 中断分布;减少盘数观察总吞吐是否保持不变。

一个慢请求在文件系统层测得 120 ms,在块设备层只有 2 ms,说明什么?

说明延迟起源于文件系统层而不是磁盘,比如等待文件系统锁、日志提交、inode 或区段分配、页缓存回收等。此时去调磁盘参数或换更快的盘没有用,应该在文件系统层继续下钻(例如用 ext4sloweroffcputime 查看阻塞栈)。

为什么说负载特征刻画往往能带来最大的性能收益?

因为它关注"施加了什么负载、为什么施加",常常能发现不必要的工作(跑飞的定时任务、重复写日志、不合理的全量扫描)。消除不必要的工作能直接去掉整块负载,收益远大于把剩余工作做得更快的调优。

vmstatwa 很高,是否足以说明磁盘是瓶颈?

不足以。wa 只表示 CPU 空闲时有任务在等 I/O,它会随 CPU 忙闲变化(CPU 忙时 wa 会下降而 I/O 问题并未改变),多核上少数线程等 I/O 也会产生 iowait。应结合 vmstatb 列、iostatawait/aqu-sz/proc/pressure/io 等指标确认。

参考资料

学完了吗?

进度保存在本机浏览器中,可在学习路线页查看。