性能问题不是猜出来的,是从系统整体逐层收敛到一行代码的证据链。本章把 CPU、内存、磁盘、网络四类指标读到能定案。

学习目标

读完本章,你应当能够:

  1. 按「系统整体 → 某类资源 → 具体进程 → 具体代码」组织排查,并说明为什么先看代码必然跑偏。
  2. 说清负载 = 可运行(R)+ 不可中断(D)的任务数与指数衰减平均口径,推出高负载低 CPU 的三种成因。
  3. 把 vmstat 各列与 top 的八项 CPU 时间对上,判断 wa 高的两种相反解释、sy 高与切换的关系、st 的含义。
  4. 用 iostat -x 的 await / aqu-sz 判断磁盘瓶颈并论证 %util 为什么不可靠;用 sar/ss/netstat -s 定位网络侧问题。
  5. 在 perf、off-CPU 火焰图、bpftrace、ftrace、/proc/[pid]/stack 之间选出能回答「进程在等什么」的那一个,并走完「P99 从 50 ms 涨到 800 ms」的排查链。

前置知识检查

你需要知道在哪一节补齐
进程状态机、D 状态、cgroup 资源限制见第 10 章 10.1 节、10.6 节
CFS 调度与上下文切换开销的量级见第 5 章 5.5 节
系统调用开销与中断路径见第 9 章 9.5 节、9.7 节
page cache 与虚拟内存的观测面见第 7 章 7.6 节、第 8 章 8.3 节

11.1 排查方法论:收敛路径与 USE 法

排查难在搜索空间太大:一次慢请求可能源于应用锁、GC、磁盘队列、宿主机超卖,各层现象相同。新手最常做的动作是打开代码找可疑循环,然后花两小时读一段本来没问题的逻辑——这条路必然失败,因为代码层是搜索空间最大、证据密度最低的一层:一个服务几十万行,而系统指标只有几十个数字。正确做法是利用「上层指标异常必在该层留下可观测痕迹」逐层排除,每层把候选原因砍掉一个数量级:

1
2
3
4
5
6
7
  收敛路径(每层只回答一个问题,答完才能下移)
  ① 系统整体  真的慢吗?慢在哪个维度?   uptime / vmstat 1 / pidstat 1
       判据:负载高? CPU 满? 内存换页? IO 排队? 网络重传?
  ② 某类资源  是 CPU / 内存 / 磁盘 / 网络 哪一类?  vmstat / free / iostat -x / sar -n DEV
       判据:该类资源的利用率或饱和度是否触及上限
  ③ 具体进程  哪个进程贡献了这个指标?     pidstat -u -w -r -d -t 1(须定位到线程)
  ④ 具体代码  它在干什么?在等谁?         perf top / off-CPU 火焰图 / /proc/PID/stack

支撑这条路径的是 USE 方法(utilization / saturation / errors):利用率回答「忙不忙」,饱和度回答「排队多长」,错误回答「有没有丢东西」。饱和度对延迟最敏感——利用率 100% 只是「没空闲」,饱和度高意味着新请求要排队,延迟立刻恶化。

资源利用率(忙不忙)饱和度(排队多长)错误(丢了吗)
CPU100 − id − wa − stvmstat 的 r、/proc/loadavg、/proc/pressure/cpudmesg 的降频与 mce
内存available / total(不是 free)vmstat 的 si/so、/proc/pressure/memoryOOM kill、memory.events 的 oom_kill
磁盘%util(须配并发度读)aqu-sz、await、/proc/pressure/iodmesg 的 I/O error
网络sar -n DEV 的带宽占比listen overflow、ss -s 的发送队列retransseg、SyncookiesSent、softnet_stat 丢包列

重要

面试的分水岭在这一节:能背 vmstat 各列的人很多,能说清「先看饱和度还是先看利用率、以及为什么」的人很少。标准答法:利用率说明资源有没有闲着,饱和度说明请求有没有在排队,决定延迟的是后者。所以看到 %util = 100% 不要下结论,看到 aqu-sz 从 1 涨到 8 才是动手信号。

11.2 平均负载:统计的是任务数,不是 CPU 使用率

11.2.1 三个数字的真实口径

/proc/loadavg 与 uptime 的三个数字是 1、5、15 分钟的指数衰减移动平均(exponentially decaying moving average),不是窗口内的算术平均。内核用定点数(FSHIFT = 11,即 FIXED_1 = 2¹¹ = 2048)周期性做指数平滑:

1
2
3
4
5
6
7
8
9
  指数衰减平均的递推(kernel/sched/loadavg.c,每个 tick 执行一次)
  active  = nr_running + nr_uninterruptible      ← 注意:D 态被算进来
  load[i] = (load[i] * exp[i] + active * FIXED_1) / (exp[i] + FIXED_1)
  三个窗口的 exp[] 常量(决定衰减快慢)
    exp[0] = 1884 ≈ 2048 / e^(5/60)    → 1 分钟窗口
    exp[1] = 2014 ≈ 2048 / e^(5/300)   → 5 分钟窗口
    exp[2] = 2037 ≈ 2048 / e^(5/900)   → 15 分钟窗口
  为什么不用算术平均:算术平均要保存窗口内全部样本,指数平均只要一个累加器,
  O(1) 空间与时间;代价是旧数据权重永不为零。与 TCP 的 SRTT 平滑(第 17 章 17.3 节)同一取舍。

警告

高频错误:把 /proc/loadavg 说成「过去 1/5/15 分钟的平均进程数」。它既不是算术平均(是指数衰减平均),也不是「进程数」而是任务数——同一进程的多个线程各算一个。一个开 200 线程的服务与一个单线程服务,同一份代码的负载口径完全不同。

11.2.2 负载的两个组成部分:R 与 D

状态内核含义为什么算进负载
R(TASK_RUNNING)正在 CPU 上跑,或在运行队列等 CPU想要 CPU 而没拿到,是真正的 CPU 需求
D(TASK_UNINTERRUPTIBLE)不可中断睡眠:等磁盘 I/O、等驱动事件它想要资源而没拿到。不可中断是因为该路径持有内核不变量,被信号打断会让结构半更新(见第 10 章 10.2 节)
S 等可中断睡眠等网络、等锁、epoll_wait、sleep不算:不消耗资源,不代表系统承压

D 态计入负载是负载指标最重要也最容易答错的一点。设计意图是:负载衡量「系统有多少活等着被干」,这个量不该只算 CPU。一块慢盘会把一批任务按在 D 态,此时 CPU 可能很闲,但系统确实忙不过来——这就是高负载低 CPU 的来源,也是 8 核机器上负载同为 8 却含义完全相反的原因。

小心

把 load average 说成「CPU 使用率」最容易把面试答崩。两者量纲不同:负载是任务个数(无上限,可到几千),CPU 使用率是时间百分比(0 ~ 100%)。64 核机器负载 64 而使用率 100%,与负载 64 而使用率 10%,是两个完全不同的问题。

11.2.3 数值口径与三种「高负载低 CPU」的原因

load ≤ 核数 说明队列基本不排队,≈ 核数 说明刚好跑满无余量,> 核数 说明开始排队,load / 核数 大致反映每个任务要等的倍率。但该判据只在负载由 CPU 贡献时成立:若负载主要来自 D 态,除以核数毫无意义,因为慢盘造成的 D 态任务数与核数无关。

三种成因的机制:其一,D 态 I/O 阻塞——任务在等磁盘、等网络文件系统、等块设备。其二,大量线程频繁唤醒——负载按 tick 采样瞬时队列长度而非累积 CPU 时间,若一批线程处于「睡极短 → 醒 → 跑极短 → 再睡」的循环(忙等、细粒度定时器、锁竞争重试、惊群),采样点容易撞上它们同时在队列里的瞬间,负载虚高而 us + sy 不高。其三,cgroup CPU 配额节流——任务确实处于 R 态,但 cpu.max(v2)/ cpu.cfs_quota_us(v1)在周期内已用完,被节流到下个周期:整机看 CPU 空闲,容器内看永远排队。

原因负载来源CPU 使用率定位命令
D 态 I/O 阻塞b 列低,wa 可能高vmstat 的 b、iostat -x、/proc/PID/wchan
线程频繁唤醒r 列瞬时抖动低但 sy 偏高vmstat 的 cs/in、pidstat -w 1
cgroup CPU 配额r 列持续非零(容器内视角)被硬顶在配额值/sys/fs/cgroup/cpu.stat 的 nr_throttled/throttled_usec 是否持续增长
1
2
3
4
5
6
cat /proc/loadavg   # 形态: 0.42 0.65 0.71 1/523 12043;第 4 字段 = 当前运行队列 1 个 / 总任务 523 个
vmstat 1 3          # 区分 R 与 D:r 列 = 运行队列,b 列 = D 态;负载的两种来源都在这
# procs -----------memory---------- ---swap-- -----io---- --system-- -------cpu-------
#  r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
#  9  0      0 812345  12345 456789    0    0     3    12  210  340  3  2 95  0  0
#  0  8      0 ...        ← r=9,b=0 是 CPU 排队;r=0,b=8 是卡在 D 态等磁盘

提示

记忆锚点:「高负载低 CPU」= 有人想要资源但拿不到,而这个资源不是 CPU。三个候选就是等盘(D)、等调度机会(唤醒风暴)、等配额(cgroup 限流)。按这个顺序答,比罗列现象有效得多。

11.3 CPU 统计:把 vmstat 的每一列对上内核计数器

11.3.1 列定义与第一次输出的陷阱

vmstat 的数据来自 /proc/stat、/proc/vmstat、/proc/meminfo,所有比率列都是「自上次采样的区间速率」,因此第一次输出是自开机以来的累计平均,必须丢弃,从第二行看。这是现场最容易踩的坑:vmstat 1 第一行显示 us=30,有人据此判断「CPU 30%」,实际那是开机至今的平均值。

列含义判据与常见误读
r运行队列中可运行的任务数(含正在运行的)持续 > 核数 说明 CPU 饱和;对应 load 的 R 部分
b处于不可中断睡眠(D)的任务数非零且持续就是 I/O 阻塞;这就是负载里 D 的部分
si / so从 swap 换入 / 换出到 swap 的速率(KB/s)判断「是否真的在用 swap」的唯一可靠信号,见 11.5.2
bi / bo块设备读入 / 写出的块数(blocks/s,1 block = 1024 B)统计块设备层 I/O;单位是块不是 KB,见下
us / sy用户态消耗 / 内核态消耗(含系统调用与部分中断处理)sy 高通常指向系统调用密集或切换密集,见 11.3.4
id空闲id 高不代表没压力:D 态任务不消耗 CPU
wa / st等待 I/O 的时间 / 被宿主机偷走的时间前者见 11.3.3;后者仅虚拟化下有,见 11.3.5

11.3.2 top 的八项 CPU 分解

top 顶部的 %Cpu(s) 与 vmstat 是同一批计数器,只是切分更细:us(用户态)、sy(内核态)、ni(nice,被调低优先级的进程所用)、id(真空闲)、wa(等 I/O)、hi(硬中断)、si(软中断)、st(被偷走)。八项在任何时刻相加恒为 100%,所以现场心算用 有效计算时间 = us + sy + ni + hi + si = 100 − id − wa − st。一个易混淆点:top 默认 Irix 模式下 us 已包含 guest 时间,按 Shift+i 切到 Solaris 模式才会分开。

字段高的时候该怀疑什么下一步命令
us应用计算密集:序列化、加密、正则回溯、GCperf top -p PID 看热点函数
sy系统调用频繁、切换频繁、内核锁竞争、网络协议栈vmstat 1 看 cs;perf top -g 看内核函数
ni有进程被调过 nice 且占用可观 CPUtop 的 NI 列
id空闲。若同时负载高,必须转查 b 列与 cgroup 配额vmstat 的 b、cpu.stat
wa见 11.3.3,两种相反解释iostat -x、pidstat -d
hi / si硬中断过密(高频小包网卡)/ 软中断过密(NET_RX、BLOCK)/proc/interrupts、/proc/softirqs
st宿主机 CPU 超卖mpstat -P ALL 1,见 11.3.5
1
2
3
vmstat 1 5                      # 每秒一行共 5 行:看趋势形态,而不是某一瞬间的绝对值
mpstat -P ALL 1                 # 按核展开,才能发现"只有 1 个核 100%、其余空闲"这类问题
top -H -p $(pgrep -f 'svc')     # -H 是关键:否则只有进程级汇总,看不到哪个线程在烧 CPU

警告

vmstat 的 bi/bo 单位是 blocks/s(1 block = 1024 B),而 iostat 的 rkB/s/wkB/s 是 KB/s。把 bo = 102400 当成 100 MB/s 会算错 1000 倍。这是报告事故时最常见的量纲错误之一。

11.3.3 wa 高不代表磁盘慢:两种相反解释

wa(iowait)对应 /proc/stat 的 iowait,内核只在很窄的条件下累加:该 CPU 空闲(没有可运行任务),同时至少有一个任务正在等本地块设备的 I/O 完成。每个限定词都重要:CPU 有活干时等待 I/O 的任务不产生 iowait,所以忙碌的机器上 wa 天然偏低,此时磁盘可能已经是瓶颈;等网络文件系统(NFS)或套接字的 I/O 不计入,所以存储全走 NFS 的服务磁盘很慢也可以 wa = 0;它是「CPU 视角的空闲归类」,iowait 期间发起 I/O 的 CPU 实际空闲,并没有被 I/O 占住。

现象可能的真相区分办法
wa 高磁盘确实慢,任务在排队iostat -x 的 await 显著高于设备固有延迟、aqu-sz 上升
wa 高磁盘其实空闲,只是该 CPU 恰好没事干而另一任务在等一次 I/O%util 低、aqu-sz ≈ 0,说明设备没排队
wa 低但应用慢磁盘是瓶颈,但 CPU 一直在忙(没机会累加 iowait)pidstat -d 看 iodelay,它按进程归属且不受 CPU 是否空闲限制

重要

标准答法的形成逻辑:wa 是「CPU 空闲且有人在等本地块设备 I/O」的时长占比,归因给 CPU,不归因给磁盘。所以它既不是磁盘利用率的代理,也不能作为磁盘瓶颈的判据——判磁盘瓶颈请看 11.6 节的 await 与 aqu-sz。

11.3.4 sy 高与系统调用、上下文切换

sy 包含三部分:系统调用执行、中断与软中断处理(扣掉单列的 hi/si)、内核线程与内核内部工作。一次系统调用的成本分四块(见第 9 章 9.5、9.7 节):特权级切换(寄存器与栈保存、syscall/sysret 本身)、地址空间相关处理(SWAPGS、返回用户态时的 TLB 处理)、参数校验与权限检查、以及执行体本身可能触发上下文切换(如 read 遇数据未就绪就睡)。因此 sy 高的成因按排查顺序是:系统调用次数太多(不是单次太贵,典型是 read/write 小包循环、futex 风暴、epoll_wait 空转,用 strace -c -f -p PID 或 perf trace 看分布);上下文切换太多(每次切换都要执行调度与地址空间切换,还破坏缓存局部性,用 vmstat 的 cs 与 pidstat -w 定位);内核锁竞争(sy 高且 CPU 大量「忙等在内核」,用 perf top -g 看内核函数)。

11.3.5 st:被宿主机偷走的时间

st(steal time)是当前 vCPU 处于可运行状态,但宿主机物理 CPU 没有调度给它的时间。它是协作式指标,只有宿主机的虚拟化层(KVM 的 kvm_steal_time 等)主动报告时才有意义,裸机恒为 0;判据是持续超过 5% ~ 10% 就值得警惕。它的关键性质是在 Guest 内部任何应用优化都无效,典型症状是「同一份代码、同样 QPS,在容器或云主机上 P99 抖动明显更大」。

注意

st 与其他字段的边界:这段时间 vCPU「想跑没跑成」,所以不计入 id(不是空闲),也不计入 us/sy(没真的执行)。这正是八项之和恒为 100% 的原因,也是排查虚拟化性能时必须把 st 单列出来看的原因:它会把真实 CPU 需求掩盖成一个偏小的 us。它由宿主机写入 Guest 内核的 per-CPU 统计,容器共享同一内核,所以只能从宿主机或节点层监控读到。

11.4 上下文切换:cs 高但不忙的典型场景

11.4.1 cs 与 in:两个常被混为一谈的计数器

1
2
3
4
5
6
7
8
  两类事件的来源与关系(/proc/stat 的 intr 与 ctxt 两个累加计数器)
  时钟/设备中断、IPI 唤醒 ──► intr ──► vmstat 的 in
  以下事件都可能引发一次切换 ──► ctxt ──► vmstat 的 cs
    ├─ 任务主动睡眠(等 I/O、等锁、等条件变量)  → 自愿切换
    ├─ 时间片耗尽被抢占 / 更高优先级任务被唤醒   → 非自愿切换
    └─ 中断返回时发现更高优先级任务可运行        → 非自愿(in 与 cs 的耦合点)
  两者不等价:一次中断不一定引发切换(处理完直接返回原任务);一次切换也不一定
  源于中断(syscall 里主动 sleep 也会切)

自愿切换由任务自己发起(等 I/O、等锁、nanosleep、futex、读空管道),高本身正常,但与低 CPU 使用率组合说明大量时间花在等待上;非自愿切换由内核抢占发起(时间片耗尽仍想跑、被更高优先级抢占),说明 CPU 争抢激烈或优先级配置不当,该任务的 CPU 需求没被满足。

11.4.2 cs 高但不忙的三类场景

「cs 几十万/秒而 us + sy 只有 20%」信息量很大:它几乎排除了「算得太多」,指向「切换得太多」。其一,锁竞争。 无竞争的 futex 快路径在用户态一次 CAS 完成(约 20 ns);一旦有竞争,等待方要进内核 futex 等待队列睡眠,持锁方释放时再唤醒——一次竞争至少带来两次切换,且唤醒后未必马上抢到锁,可能反复「睡 → 醒 → 抢不到 → 再睡」。其二,频繁唤醒(唤醒风暴 / 惊群)。 事件源一次到达唤醒大量等待者,或线程池里每个任务都极短;唤醒目标若在不同 CPU 还会触发核间中断(IPI),in 跟着涨。其三,细粒度定时器与轮询退避。 大量任务以毫秒甚至更小间隔 nanosleep,或 select/poll 超时值极小导致空转。

1
2
3
pidstat -w 1 3                          # 列: UID PID cswch/s nvcswch/s Command;cswch/s 高 → 在等资源
pidstat -w -t -p $(pgrep -f 'svc') 1    # nvcswch/s 高 → 抢不到 CPU;-t 暴露"哪个线程在频繁睡醒"
perf record -e sched:sched_switch -a -- sleep 5 && perf script | head -50   # 追踪 prev/next 配对

警告

常见错误回答:「上下文切换的开销就是保存寄存器,很快」。真实成本是四块:寄存器与栈的保存恢复(约 1~5 μs)、地址空间切换导致的 TLB 与缓存局部性损失(大头,可能让之后几千条指令全部缓存未命中,见第 1 章 1.4 节)、调度器决策、以及新任务预热。所以减少切换次数的收益远大于让切换更快。

11.5 内存指标:free 的每一列与 available

11.5.1 free 输出的逐列解释

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
  free -m 的列布局(Linux 3.14 之后)
                 total    used    free   shared  buff/cache   available
  Mem:           15883    6210    1893      412        7779        8842
  Swap:           4095     128    3967
  total      物理内存总量(MemTotal)
  used       = total − free − buff/cache
  free       完全未被引用的内存;它是"最没用"的那个数字
  shared     tmpfs/shmem 占用(/dev/shm、共享内存段),已被计入 cache
  buff       块设备元数据缓存(buffer_head 层面),典型只有几十~几百 MB
  cache      page cache(文件内容 + tmpfs + 可回收 slab),这才是大头
  available  free + 大部分可回收 cache/slab − 内核保留量(内核估算"不用 swap 还能给新应用多少内存")
列的常见误读为什么错正确读法
「free 只剩 200 MB,赶紧扩容」page cache 会被优先用作缓存,可随时回收看 available / total 与 /proc/pressure/memory
「used 高就是内存泄漏」used 不含 buff/cache,但含不可回收 slab 与共享内存按进程看 RSS/PSS:/proc/PID/smaps_rollup
「buff/cache 是应用缓存,删掉能省内存」它就是 page cache,删掉只会在下次读盘时重建drop_caches 只做实验,不做优化
「buff 与 cache 是两个独立可加的量」2.4 起两者已统一到同一套页管理;shared 也已被计入 cache分开看,不做加法

available 由内核 si_mem_available() 估算(/proc/meminfo 的 MemAvailable):从 free 出发,加上可回收 page cache 的绝大部分、可回收 slab 的一部分,再减去 min_free_kbytes 这类保留量。它是估算值,所以判据要看比例与趋势,不要卡在几百 KB。真正内存不足的签名是三条同时出现:available 逼近 0、buff/cache 被压得很低(说明回收一直在工作)、si/so 开始动。

11.5.2 si / so:是否在用 swap 的唯一可靠信号

vmstat 的 si(从 swap 换入)与 so(换出到 swap)是唯一可靠的「是否真的在用 swap」信号。原因:free 的 Swap: used 只是「swap 里目前有多少页」,它只反映历史压力——一台机器可能在几分钟突发中换出 2 GB,之后内存宽裕了,这些页仍躺在 swap 里(换入是惰性的,被访问时才换回)。若 si/so 持续非零,确实不够,损失机制也明确:换页按页(4 KB)同步进行,一次换入可能是一次磁盘读;若换入的页之后又要换出别的页,就形成 swap 抖动(thrashing),CPU 大量时间耗在页替换上,吞吐断崖式下跌。此时 wa 未必高,因为换页等待发生在缺页处理路径上,属于进程自己的执行时间。反之 si/so 全为 0 而 Swap used 非零只说明历史上换过,现在不在动,不构成当前性能问题。

1
2
3
vmstat 1 5                        # 看 si/so 两列是否有持续非零,这才是"正在换页"
cat /proc/pressure/memory         # avg10 = 最近 10 秒内至少一个任务因内存而停顿的时间占比
cat /sys/fs/cgroup/memory.events  # 容器内看 cgroup v2 事件:关注 max / oom / oom_kill

重要

标准答法:「判断是否在用 swap,看 vmstat 的 si/so 是否持续非零,不要看 free 的 Swap used。」能补上「Swap used 是存量、只反映历史压力」与「vm.swappiness 的默认 60 决定的是换出倾向而不是『是否允许换出』」,就领先绝大多数候选人。注意 swappiness = 0 也不等于禁止换页,内存严重不足时内核仍会换出(见第 12 章)。

11.6 磁盘 I/O:await 与 aqu-sz,以及 %util 为什么不可靠

11.6.1 iostat -x 的关键列

iostat -x 的数据源是 /proc/diskstats,它默认打印所有设备(含分区),看分区行与整盘行容易重复计数,务必用 iostat -x 1 nvme0n1 限定设备。

列(iostat -x 常见名)旧名含义判据
r/s、w/stps每秒读/写请求次数(IOPS 的两个分量)与 await 一起决定吞吐上限
rkB/s、wkB/skB_read/s 等每秒读/写的千字节数除以 r/s 得平均请求大小;小请求 + 高 IOPS 即随机负载
r_await、w_await—读/写请求平均完成延迟(ms),含排队 + 设备服务时间与设备固有延迟比较,见 11.6.3
await—读写延迟的加权平均混合负载下会掩盖读或写的异常,读写要分开看
aqu-szavgqu-sz平均在途请求数(队列长度)> 1 说明有排队;判断饱和度的首选
%util—「设备至少有一个 I/O 在处理」的时间占比见 11.6.2,不可靠
rareq-sz、wareq-sz—读/写平均请求大小(KB)≥ 128 KB 偏顺序,4~16 KB 偏随机
%rrqm、%wrqm—被合并的请求占比合并率高说明上层 I/O 模式接近顺序

11.6.2 %util 为什么不可靠

%util 的定义是「采样区间内该设备有至少一个 I/O 在途的时间占比」。对单队列设备它近似等于「忙」的比例,但对现代设备这个类比彻底失效,因为一台设备可以同时处理多个 I/O:

1
2
3
4
5
6
7
8
9
  并发 I/O 下 %util 与真实余量的关系(同一块 NVMe SSD)
  情形 A:单条流同步逐笔下发        情形 B:32 条流并发
    t=0 ├── req1 ──┤                  t=0 ├── req1 ─────────────┤
    t=L    ├── req2 ──┤                   ├── req2 ─────────────┤
    t=2L      ├── req3 ──┤                ├── ... 共 32 个 ─────┤
                                          ├── req32 ────────────┤
    %util = 100%(每刻都有 1 个在途)  %util = 100%(同样);实际 IOPS = 1/L vs 32/L(高 32 倍)
  ⟹ %util 只能回答"设备有没有闲着",不能回答"还有多少余量"
  RAID / 多队列更极端:逻辑卷背后 8 块盘并行,%util = 100% 而每块盘 aqu-sz 只有 0.5,余量充裕。

正确结论:%util 高 ≠ 磁盘是瓶颈,它只说明设备一直在处理 I/O;当在途并发远大于设备最佳并发度时 %util 必然 100%。反过来 %util 低也不代表磁盘没问题:等待可能发生在网络文件系统上,而它根本不计入块设备统计。

11.6.3 判断磁盘瓶颈的正确口径

一是看 await 是否显著高于设备固有延迟。数量级参照(4 KB 随机读,单次设备延迟):NVMe SSD 约 100 μs、SATA SSD 约 100200 μs、机械盘约 510 ms。若 NVMe 报出 w_await = 5 ms,那是排队而非设备慢。难点在于必须知道这台设备的基线,所以生产上看相对变化(与同集群同型号节点对比)而非绝对值。二是看 aqu-sz 与并发度的关系:在途请求数直接反映饱和度,由 Little 定律 aqu-sz ≈ 到达率 × await 可用它交叉验证报告数字是否自洽,若不自洽(虚拟化逻辑卷常见)本身就是重要线索;aqu-sz 接近或超过设备最佳队列深度时,再加负载只增延迟不增吞吐(IOPS = Q / L 的关系见第 9 章 9.9.1 节)。三是看 r_await 与 w_await 是否分离:混合负载下 await 会被平均掉,一次冷读的长延迟可能被大量写请求稀释,读写分开后常能发现「写在排长队,读很轻松」。

1
2
3
iostat -x 1 nvme0n1        # 限定目标设备;5.x 列含 r_await/w_await/aqu-sz/%util
pidstat -d 1               # 按进程归因:谁在产生这些 I/O,iodelay 暴露"在等 I/O"的时间
blktrace -d /dev/nvme0n1 -o - | blkparse -i - | head -40   # 用延迟分布代替平均值:长尾才是 P99 的根因

小心

面试最容易答崩的一句是「%util 到 100% 说明磁盘到瓶颈了」。正确的反问式回答是:「%util 只说明设备一直在处理 I/O,它不告诉你还有多少余量;判断饱和要看 aqu-sz 与 await。在并发 I/O 或 RAID 场景下,%util = 100% 完全可以健康。」 反过来也要会答:%util 低同样不能证明磁盘没问题,因为等待可能发生在网络文件系统上。

11.7 网络指标:sar、ss、netstat -s 的排障含义

11.7.1 sar -n 子开关

命令输出什么关键列与判据
sar -n DEV 1每网卡收发速率rxkB/s/txkB/s 与线速比较;rxpck/s 与 txpck/s 相除得平均包长,包长小说明小包密集(每包协议栈固定开销大,pps 才是瓶颈)
sar -n TCP 1连接层状态active/s、passive/s(新建连接速率)、iseg/s/oseg/s。active/s 远大于 passive/s 说明本机是客户端角色
sar -n SOCK 1Socket 总量totsck、tcp-tw(TIME_WAIT 数量)、tcpsck。tcp-tw 暴涨会耗尽本地端口(见第 16 章)
sar -n EDEV 1网卡错误与丢弃rxdrop/s、txdrop/s、rxerr/s。非零说明链路侧已在丢,这类丢弃常被应用层重传掩盖

11.7.2 ss -s、netstat -s 与它们的坑

ss -s 给的是存量快照:各类 socket 的当前数量、内核态内存占用、按状态的 TCP 连接计数,回答「现在有多少连接、卡在哪个状态」。netstat -s(或 nstat)给的是累计计数器,判据是看增量而不是总量——累计值只增不减,总量里的历史重传毫无意义。

计数器(netstat -s 中的行)含义排障含义
segments retransmitted重传段数除以 out segments 得重传率:局域网应 < 0.1%,广域网 < 1%。偏高指向丢包、链路错误或对端接收窗口不足
SYNs to LISTEN sockets droppedSYN 队列溢出tcp_max_syn_backlog 太小或遭 SYN Flood,与 SyncookiesSent 对照
times the listen queue of a socket overflowedaccept 队列溢出这是应用的锅:accept() 太慢,已完成三次握手的连接被丢弃,客户端超时重试。这是「服务端有空闲 CPU 却拒绝连接」的经典根因(见第 16 章)
packets pruned from receive queue接收缓冲区不足应用读得慢,内核丢包,触发重传与窗口收缩
SyncookiesSent / SyncookiesRecv启用了 SYN cookie说明半连接队列确实溢出过,是被动防护生效的证据,不是正常状态
TCPAbortOnMemory / TCPBacklogDrop内存压力下丢连接需与连接数上限、tcp_mem 一起看
1
2
3
4
5
nstat -az > /tmp/s1; sleep 10; nstat -az > /tmp/s2        # 取两次快照求差才是判据
awk 'NR==FNR{a[$1]=$2; next} $1 in a && $2!=a[$1] {print $1, "+"($2-a[$1])}' /tmp/s1 /tmp/s2 | sort -k2 -nr | head -20
# 输出形态: TcpRetransSegs +1842      ← 10 秒内 1842 次重传,该查链路了
ss -s      # 存量视角: Total: 1234  TCP: 890 (estab 512, closed 300, timewait 280)
ss -tan state time-wait | awk '{print $4}' | cut -d: -f2 | sort | uniq -c | sort -nr | head   # ss 走 netlink,比 netstat 快

11.8 高级工具的选择:谁在算 vs 谁在等

前七节回答「哪类资源饱和了」。最后一步要回答「具体是哪行代码、或者它在等什么」,工具选择的唯一依据是问题的形状:

1
2
3
4
5
6
7
8
9
  按问题形状选工具(选错的代价是两小时白干)
  Q1 "CPU 在算什么?"   → perf top / perf record + on-CPU 火焰图(只看正在 CPU 上执行的栈)
  Q2 "进程为什么不在跑?" → off-CPU 火焰图 / /proc/PID/stack + wchan
     · 关键区分:等锁(off-CPU,可被唤醒)vs D 态(不可中断);落地靠 eBPF 追踪
       sched_switch,统计"从切出到切入"的时长与栈
  Q3 "调了哪些系统调用、哪个最慢?" → strace -T -c / perf trace
     · strace 每次 syscall 两次 ptrace 停,开销巨大,只适合短时或低频进程
  Q4 "内核某函数被调了多少次?" → bpftrace / ftrace(内核内建、零额外依赖)
  Q5 "延迟的长尾分布长什么样?" → bpftrace 直方图 / perf latency(平均值骗人,必须看 P99/P999)
工具最好回答的问题主要代价 / 边界
perf top / perf record -g + 火焰图on-CPU 热点函数与调用链归属(谁调用了那个热点)只有 on-CPU 视角;受 perf_event_paranoid 限制;采样频率过低会漏长尾
off-CPU 火焰图进程不在 CPU 上的时间花在哪(等锁、等 I/O、被抢占)需 eBPF(bcc 的 offcputime)或 perf sched;用户态栈符号缺失则无法归因到业务代码
bpftrace / ftrace一行脚本回答特定问题(谁在 open、read 大小分布、谁在等 futex);ftrace 可看内核调用链耗时需内核 4.x+ 与 CONFIG_BPF;脚本本身可能成为新的开销源;function_graph 在热路径开销大
strace -T -c系统调用次数与耗时分布ptrace 开销可达数量级,对高 QPS 服务不可用,会先把它拖死
/proc/PID/stack、wchan某个进程此刻卡在哪(内核栈 + 等待函数名)需 root;wchan 在 4.1+ 默认只给地址
1
2
3
4
5
6
7
8
9
perf top -p $(pgrep -f 'svc') -g      # on-CPU 热点:加 -p 只看该进程,不加是全局视角
perf record -F 999 -g -p PID -- sleep 30 && perf script | stackcollapse-perf.pl | flamegraph.pl > oncpu.svg   # 火焰图脚本来自 Brendan Gregg 开源仓库
offcputime -df -p PID 30 > offcpu.folded        # off-CPU:等锁/等 I/O 的时间去哪了(bcc,需 eBPF)
flamegraph.pl --color=io --title="Off-CPU" offcpu.folded > offcpu.svg
bpftrace -e 'kprobe:futex_wait { @[ustack] = count(); }'          # 一行脚本:谁在等 futex(锁竞争的第一现场)
echo function_graph > /sys/kernel/tracing/current_tracer          # ftrace:追踪内核函数的调用链
for p in $(ps -eo pid,state | awk '$2=="D"{print $1}'); do
    echo "PID $p wchan=$(cat /proc/$p/wchan)"; cat /proc/$p/stack | head -8   # D 态进程卡在哪个函数,直接指向根因
done

提示

面试话术锚点:「perf top 看到的是 CPU 在算什么,看不到进程在等什么。等锁、等 I/O、等网络的时间不在任何 CPU 热点里,只能靠 off-CPU 火焰图或调度事件追踪。」 这能立刻把「只会用 perf top」和「真的排查过线上问题」区分开。追问「off-CPU 的栈怎么来」——答 eBPF 挂 sched_switch,记录切出时刻与栈、切回时求差并按栈聚合(offcputime 就是这个思路)。

11.9 完整案例:接口 P99 从 50 ms 涨到 800 ms

设定:无代码变更、无发布,流量与前一天同一时段持平,但 P99 从 50 ms 涨到 800 ms,P50 只从 20 ms 涨到 35 ms。P50 几乎没动而 P99 涨了 16 倍这个形状本身排除了「整体资源不足」(那会同时抬高 P50),指向「部分请求被某个偶发事件阻塞」。

第一步看系统整体:vmstat 若给出 r=2, b=11, si=so=0, id=88, st=0,则负载高(r + b 超过 8 核)但 CPU 空闲(id = 88),且 b = 11——这正是 11.2.3 节「原因一:D 态 I/O 阻塞」的签名;si/so = 0 排除内存换页,st = 0 排除虚拟化超卖。第二步验证块设备侧是否真的排队:

1
2
3
4
uptime; nproc; cat /proc/loadavg; vmstat 1 5    # 一次拿到负载、核数与 r/b/si/so/cs 的轮廓
iostat -x 1 nvme0n1
# 稳态行: r/s=12 r_await=1.20  w/s=340 w_await=48.60  aqu-sz=17.20  %util=99.80;判据:NVMe 写延迟基线约 0.1~1 ms,48.6 ms 是排队而非设备慢,aqu-sz=17.2 说明在途已堆满
df -hT | awk '{print $1, $2, $7}'               # 确认落盘的是哪一类文件系统

这里的关键是交叉验证:w_await 与 aqu-sz 同时恶化,两个独立指标指向同一结论,才是可靠证据——单看 %util = 99.8% 不足以下结论(11.6.2 节)。同时要排除一个常见误判:若 df 显示数据落在 NFS 上,这笔 I/O iostat 根本看不到(iowait 也不计),此时要换 nfsiostat、mountstats。第三步把写入归因到进程与线程,第四步才读代码——而第一件事不是读代码,是读内核栈:

1
2
3
4
5
pidstat -d 1 3                                     # kB_wr/s 找出写入大户;iodelay 高说明真在等 I/O
ps -eo pid,stat,wchan:24,comm | awk '$2 ~ /^D/'    # 形态: 8421 D jbd2_log_wait_commit java ← 卡在文件系统日志提交
cat /proc/8421/stack     # 自顶向下是调用链的反序
# [<0>] __flush_work+0x1a2/0x2c0 / journal_commit_transaction+0x8f1 / kjournald2+0x9d
# ⟹ ext4 的日志提交线程 jbd2 被块设备写延迟拖住

定位结论。 b = 11 与 w_await = 48.6 ms 说明块设备写队列已饱和;pidstat -d 把写入归因到某个进程;/proc/PID/stack 显示它卡在文件系统日志提交上。根因是写 I/O 饱和导致的 D 态阻塞:某进程开始大量写盘,同盘上的其他进程一起被拖慢,每次写请求排队约 50 ms,应用线程被按在 D 态,于是 P99 跳到数百毫秒。而 P50 几乎不动,是因为只有命中这条写路径的请求才被阻塞:读缓存命中的请求不受影响(占多数,P50 稳定),恰好需要写盘或读冷数据的请求才排队(少数,构成 P99)——这就是尾延迟放大的一般规律。

处置与验证(按收益/风险排序)。 一是先止损、不动代码:确认是不是临时批量任务(备份、日志轮转、数据搬迁)在占盘,错峰即可。二是减写入量:调低日志级别、合并小写、批量提交(把 N 次 fsync 合成一次,即组提交思路,见第 21 章 21.3 节)。三是隔离 I/O:日志/备份/数据库拆到不同块设备,用 cgroup v2 的 io.max 或 io.latency 给关键进程设保护(见第 10 章 10.6 节),注意 io.max 限带宽/IOPS 而 io.latency 限延迟,后者对尾延迟更对症。四是确认后再扩容:只有在减少无效写入并隔离之后仍饱和,才考虑加盘或换更高 IOPS 的设备。五是验证:vmstat 的 b 回到 0 附近、aqu-sz 落到 1 以下、w_await 回到设备基线、P99 回落,四项都要变好才算解决。

重要

这个案例的价值不在结论,而在于每层都有排除法的动作:第一步用 si/so 与 st 排除内存和虚拟化,第二步用 await + aqu-sz 双指标确认磁盘饱和(而不是只信 %util),第三步用 pidstat -d 归因到进程,第四步用 /proc/PID/stack 归因到等待对象。面试时把这条链讲完整,比背任何结论都有说服力。

本章小结

关键结论一句话表述常见错误
收敛路径与 USE每层只回答一个问题;利用率看忙不忙,饱和度看排队多长,延迟由饱和度决定上来就读代码;只看利用率,看到 id 高就认为健康
平均负载可运行(R)+ 不可中断(D)的任务数,1/5/15 分钟指数衰减平均,须除以核数说成「CPU 使用率」或「算术平均的进程数」
vmstat / wa第一次输出是累计平均必须丢;bi/bo 单位是 blocks/s;wa 是「CPU 空闲且有人在等本地块设备 I/O」,归因给 CPU用第一行判断当前 CPU;认定 wa 高就是磁盘慢
sy / stsy 指向系统调用次数、切换次数、内核锁竞争;st 是被宿主偷走的时间,容器内不可见说「切换开销只是保存寄存器」;把云上抖动全归因到应用
内存与 swap看 available 而非 free;buff/cache 是合并列且 shared 已计入 cache;si/so 持续非零才是真换页free 小就扩内存;看到 Swap used 非零就断定内存不足
磁盘判据用 await 对比设备基线、用 aqu-sz 看并发度;%util 只说明设备没闲着说「%util 100% 就是磁盘瓶颈」
网络判据累计计数器要取增量算比率;listen queue overflow 是应用 accept 慢直接读 netstat -s 的绝对值下结论
工具选择perf top 看 on-CPU 在算什么;off-CPU 火焰图看它在等什么;/proc/PID/stack 看卡在哪用 perf top 找阻塞问题,自然什么都找不到

自测题

Q1(判断 + 推导)某 8 核机器 uptime 显示 load average: 32.00, 30.50, 28.00,而 top 显示 5.0 us, 3.0 sy, 90.0 id, 0.0 wa, 0.0 st。请判断是否存在 CPU 瓶颈,并给出至少三种可能的解释与对应验证命令。

答案与解析

不存在 CPU 瓶颈。判据有两条:us + sy = 8%,CPU 大量空闲;而 load / 核数 = 4 看似「超载 4 倍」,但这个判据只在负载由 CPU 贡献时有效。负载 = R + D,id = 90% 说明运行队列不长,那 32 个任务必然主要来自 D 态。

  1. D 态 I/O 阻塞:vmstat 1 看 b 是否约等于 32;ps -eo stat,pid,comm | awk '$1 ~ /^D/';iostat -x 1 看 await/aqu-sz;/proc/PID/stack 看栈顶等待函数。
  2. 线程频繁唤醒导致队列瞬时虚高:vmstat 的 cs/in 与负载同向飞涨;pidstat -w 1 看谁的自愿切换率极高。此时 wa ≈ 0,可与第一种区分。
  3. cgroup CPU 配额节流:/sys/fs/cgroup/cpu.stat 的 nr_throttled/throttled_usec 持续增长(仅「非零」不算,要看增长);cpu.max 看配额。整机空闲与容器内排队可以同时成立。

错在哪:把 load / 核数 = 4 直接读成「CPU 严重不足」。面试官会追问「wa 是 0,你怎么还说是 I/O 问题?」——wa 只在 CPU 空闲时才累加,所以 wa = 0 在此恰好排除了「本地块设备等待」,把方向推向 cgroup 节流、唤醒风暴,或网络文件系统(NFS 等待不计入 iowait)。这一问考的就是 wa 的口径。

Q2(计算 + 辨析)某 NVMe SSD 的 iostat -x 为 w/s = 4000、w_await = 3.5(ms)、aqu-sz = 14.0、%util = 100.00。请计算写吞吐与每个请求的平均停留时间,判断是否已饱和,并说明 %util = 100% 能否作为瓶颈证据。

答案与解析

写 IOPS = w/s = 4000。由 Little 定律 aqu-sz ≈ 到达率 × 停留时间:Q = 4000 /s × 3.5 × 10⁻³ s = 14.0,与报告的 aqu-sz 一致——说明报告数字自洽可信,可以放心用它判断。设备服务时间(不含排队)无法从这些列直接得出,必须与基线比较:若同型号设备在 aqu-sz ≈ 1、w_await ≈ 0.1 ms 时也能达到 4000 IOPS,那么现在这 3.5 ms 里约 3.4 ms 是排队,设备已经饱和——它跑在吞吐上限附近,再加负载只会增加延迟而不再增加 IOPS。

%util = 100% 不能单独作为瓶颈证据。它的定义是「设备至少有一个 I/O 在处理的时长占比」,aqu-sz = 14 时设备当然每刻都有在途请求,所以 %util 必然 100%——这是定义上的必然,不携带「还有没有余量」的信息。一块能并发 64 个请求的 NVMe 在 aqu-sz = 14 时可能仍有余量,%util 依然是 100%。

错在哪:用 %util = 100% 直接下结论,答不出它的定义;以及忽略 aqu-sz 与 await 的自洽性检验(虚拟化逻辑卷上这两值常不自洽)。面试官会追问「你怎么知道设备上限」——用 fio 压出该型号的 IOPS-延迟曲线做基线(见第 13 章 13.5 节)。

Q3(推导)free -m 显示 total = 16384、used = 15800、free = 120、buff/cache = 464、available = 380。请判断是否内存不足,并说明为什么 available 与 free 差得这么小。

答案与解析

这是内存不足的信号。available = 380 MB 约占 total 的 2.3%;更要紧的是 buff/cache 只剩 464 MB——正常运行的 Linux 上 page cache 通常能占内存一半以上,cache 被压到这么低说明回收(reclaim)一直在工作,反复释放缓存去满足分配。

两者关系是 available ≈ free + 大部分可回收 page cache 与 slab − 内核保留的低水位。正常时 available 远大于 free(因为 cache 很大);这里只差 260 MB,说明可回收部分几乎被榨干,available 退化成「free 加一点点」。这个差值本身就是压力指标,比绝对值更早预警。还要补两件事:是否正在换页(vmstat 1 的 si/so 持续非零才是铁证,Swap used 非零不能证明什么);回收压力与 OOM 风险(/proc/pressure/memory 的 avg10、dmesg -T | grep -i oom、容器内 memory.events 的 oom_kill,见第 12 章 12.5 节)。错在哪:只答「free 小是正常的,要看 available」——通则本身对,但本例 available 也小,结论应是内存不足。这是把正确的通则套到不满足其前提的数据上。面试官会追问「available 怎么算」:内核 si_mem_available() 从 free 出发,加上可回收 page cache 的绝大部分与可回收 slab 的一部分,再减去 min_free_kbytes 等保留量;它是估算值,判据看趋势与量级。

Q4(设计)一个 Java 服务在容器中运行,容器内 CPU 稳定 200%(配额 2 核),吞吐比物理机低 40%,而宿主机 mpstat -P ALL 1 显示多核 %steal 在 8% ~ 15% 波动、%idle 仍有余量。请给出排查路径与处置建议,并说明为什么「优化 Java 代码」在这里无效。

答案与解析

先分清两个独立的限流来源。来源一:cgroup CPU 配额。 容器内 CPU 顶在 200%(正好是 cpu.max 的 2 核)。验证:cat /sys/fs/cgroup/cpu.max(v2 格式 quota period,如 200000 100000 即 2 核);cpu.stat 的 nr_throttled 与 throttled_usec 是否持续增长——只有持续增长才说明真被节流。此时容器内负载(R)很高而整机 id 仍有空闲,正是 11.2.3 节第三种成因。

来源二:宿主机 steal time。 %steal 8% ~ 15% 意味着 vCPU 可运行但物理 CPU 没分给它。这部分损失在容器内任何指标上都看不出来(表现为「时间变慢了」而非「被等待了」),只能从宿主机或云厂商监控读到;%idle 仍有余量说明不是物理 CPU 全满,而是虚拟机与其他租户争抢(超卖)。处置:先在容器内确认节流(cpu.stat、/proc/pressure/cpu);再在宿主机确认 steal 分布——集中在少数核指向亲和/超线程争抢,均匀分布指向整体超卖;配额不足就提高 cpu.max,超卖导致的偷取只能靠换宿主机、换规格、迁移独享型实例。

为什么优化代码无效:两个来源都在应用之外——配额是 cgroup 调度器在周期边界拒绝给 CPU,steal 是宿主机虚拟化层不给物理 CPU。代码优化得再快,%steal 那几十毫秒里它也执行不到指令。能优化的是「在给定算力下做更多事」(减少系统调用与切换、减少 GC 停顿),但「把被偷走的时间拿回来」超出应用权限边界。错在哪:看到 CPU 200% 就断言「CPU 打满了,加机器」;正确顺序是先分清「被自己算满」还是「被配额或宿主机限住」。面试官会追问「%steal 在容器里能看到吗」——不能:它由宿主机虚拟化层写入 Guest 内核的 per-CPU 统计,容器共享同一内核。

Q5(推导 + 设计)vmstat 1 持续显示 cs = 400000、in = 120000,而 us = 12%、sy = 18%;pidstat -w -t 显示某进程 cswch/s 高达 30 万/秒、nvcswch/s 只有几百。请判断属于哪一类问题,给出把范围收窄到「具体是哪把锁」的命令序列,并说明为什么这里不该优先调内核参数。

答案与解析

判断:自愿上下文切换占绝对多数(30 万 vs 几百),说明任务不是在抢 CPU(否则非自愿切换该高),而是在反复主动睡眠等待某个资源;CPU 合计仅 30%,排除了计算密集。为什么更可能是锁竞争而非普通 I/O 等待:in 也高而 wa 未升高,说明不是块设备等待;cs/cswch 的量级(几十万/秒)远超正常 I/O 等待频率。锁竞争的特征是同一把锁上形成「睡眠 → 唤醒 → 抢不到 → 再睡眠」循环,每次至少两次切换,切换率被放大到远超实际等待次数。

1
2
3
4
5
pidstat -w -u -t -p <PID> 1 5                                    # 线程级切换率与 CPU
bpftrace -e 'kprobe:futex_wait { @[kstack, ustack] = count(); }'  # futex 是用户态锁入口:统计锁地址与栈
perf lock record -p <PID> -- sleep 10 && perf lock report        # 内核视角的锁竞争统计
perf record -e sched:sched_switch -e sched:sched_wakeup -a -- sleep 5   # 配对出"睡眠-唤醒"对
offcputime -df -p <PID> 30 > offcpu.folded                       # off-CPU 火焰图:按栈聚合等锁时间

为什么不该先调内核参数:症状是应用内部在锁上反复睡眠,属于并发设计问题(临界区过长、锁粒度过粗、用全局锁保护本可分段的状态、CAS 重试风暴),改 vm.max_map_count、vm.nr_hugepages 之类参数不会减少任何一次 futex_wait;正确方向是缩小临界区、按业务键分段加锁、改用分片或无锁结构、或用批处理摊薄唤醒次数。错在哪:只看 cs 绝对值就断言「系统忙着切换所以要减线程数」——线程数不是根因,每个线程为什么反复睡醒才是;以及混淆自愿与非自愿切换,把「等锁」误判成「抢 CPU」。面试官会追问「你怎么证明是同一把锁」——统计 futex_wait 的锁地址分布或 kstack/ustack 聚合结果。

## 面试高频问题与陷阱
面试问法考查角度陷阱 / 加分点对应小节
平均负载是什么意思?那三个数字分别代表什么?负载口径答「CPU 使用率」或「过去 1/5/15 分钟的算术平均进程数」直接扣分;必须说清「可运行 R + 不可中断 D 的任务数 + 指数衰减平均」,并主动补一句「要与核数相除才有意义」11.2
负载很高但 CPU 使用率很低,你会怎么查?高负载低 CPU只答「有进程在死循环」——那会推高 CPU;正确答法给三种成因:D 态 I/O 阻塞、线程频繁唤醒、cgroup 配额节流,每种配一条验证命令11.2.3
wa 很高是不是说明磁盘到瓶颈了?iowait 口径答「是」是最典型的错;wa 是「CPU 空闲且有人在等本地块设备 I/O」的时长,归因给 CPU 不归因给盘;还要能举出 NFS 等待不计入、CPU 忙时 wa 天然偏低这两个反例11.3.3
%util 到 100% 了,是不是磁盘瓶颈?%util 定义答「是」会被立刻追问倒;说明它只是「设备至少有一个 I/O 在处理的时长占比」,不携带余量信息,判据应是 await 对比基线加 aqu-sz11.6.2
free 显示内存快用完了,是不是要扩内存?内存指标口径答「free 小就危险」;正确答法是看 available 占 total 的比例,并解释 page cache 可回收、buff/cache 是合并列且 shared 已计入 cache11.5.1
你怎么判断系统是不是在用 swap?swap 的唯一可靠信号答「看 free 的 Swap used 是不是 0」是错的;正确答法是 vmstat 的 si/so 是否持续非零,并说明 Swap used 只是存量、反映历史压力11.5.2
你的服务 P99 突然变高但 P50 正常,怎么排查?尾延迟归因答「整体资源不足」——那会同时抬高 P50;正确思路是「只有部分请求命中某条慢路径」,用四板斧逐层收敛到具体阻塞点11.9
perf top 能看到进程在等锁吗?on-CPU 与 off-CPU答「能」是概念性错误;perf top 采样的是正在 CPU 上执行的栈,等锁/等 I/O 的时间不在任何 CPU 上,要用 off-CPU 火焰图或调度事件追踪11.8
listen queue overflow 涨了,是网络问题还是应用问题?accept 队列语义归因为网络或内核参数;正确答法是「已完成三次握手的连接因 accept() 不及时被丢弃」,属于应用并发不足或 accept 循环被阻塞11.7.2

延伸阅读

  • man 1 vmstat、man 1 iostat、man 1 pidstat、man 1 sar、man 1 mpstat(sysstat 的官方列定义;bi/bo 的单位与 aqu-sz 的改名说明都在这里)。
  • man 5 proc(/proc/loadavg、/proc/stat、/proc/meminfo、/proc/PID/stack、wchan、schedstat 的字段定义)、man 8 ss、man 8 netstat。
  • Linux 内核源码:kernel/sched/loadavg.c(calc_load 与 exp[] 常量、LOAD_FRAC_BITS)、mm/page_alloc.c 的 si_mem_available()(MemAvailable 的估算口径)、block/blk-core.c(iostat 各列的计数器来源)。
  • Brendan Gregg, Systems Performance: Enterprise and the Cloud(第 2 版第 2 章 USE 方法;第 4~10 章分别对应本章四类资源)、BPF Performance Tools(off-CPU 分析与 bpftrace 单行脚本目录)。
  • Linux 内核文档:Documentation/accounting/psi.rst(/proc/pressure/* 的语义与阈值接口)、Documentation/admin-guide/cgroup-v2.rst(cpu.stat 的 nr_throttled/throttled_usec,io.max 与 io.latency 的差别)、Documentation/trace/ftrace.rst。
  • fio 官方 HOWTO(iodepth、bs、rw 参数与 IOPS-延迟曲线的测法,与第 13 章 13.5 节配合阅读)。