Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

A.3 排错手册:症状 → 假设 → 证据

1. 排错不是猜,是收敛

一条纪律贯穿全章:

现象(可观察) → 假设(可证伪) → 证据(命令输出) → 结论(改什么)

每次只验证一个假设;每个假设先写「如果成立,应该看到什么」。没有证据的结论叫猜测,不叫排错。

1.1 证据日志模板

## 2026-10-18 14:02 服务 5xx 升高
- 环境:Ubuntu 24.04 / kernel 6.8.0 / 8 vCPU
- 现象:5xx 从 0.1% 升到 12%,P99 从 20ms 到 2s
- 最近变更:14:00 发布 v1.4.2(改了连接池大小)
- 假设 1:连接池耗尽 → 证据:`ss -s` 显示 TIME_WAIT 3.2 万
- 假设 2:下游超时 → 证据:`curl -w` 下游 P99 正常
- 结论:回滚连接池参数;后续加 pool wait 指标

2. CPU 高

2.1 先分清用户态、内核态、软中断、偷取

top -b -n1 | head -15
mpstat -P ALL 1 3
现象可能原因下一步
%us 高应用计算/死循环/正则回溯perf top -p PID
%sy 高系统调用多/锁竞争/内存拷贝strace -c、perf top 看内核符号
%si 高网络软中断/NAPI/proc/softirqs、softnet_stat、RPS
%wa 高I/O 等待iostat -xz、biolatency
%st 高虚拟机被宿主抢占换宿主机/查超卖

2.2 用户态热点

perf record -F 999 -g -p $PID -- sleep 10
perf report --stdio --sort comm,dso,symbol | head -40

读法:

  • Children 高、Self 低:热点在子调用,继续展开;
  • Self 高:函数本身在烧 CPU;
  • 大量 memcpy/malloc/free:看是否拷贝过多、分配过碎;
  • 大量 __lll_lock_wait:锁竞争,转 perf lock 或 offcputime。

2.3 内核态热点

sudo perf top -g --sort dso,symbol
sudo bpftrace -e 'profile:hz:99 /pid == '"$PID"'/ { @[kstack] = count(); }'

常见:

  • tcp_sendmsg/copy_user_enhanced_fast_string:发送拷贝;
  • __alloc_pages/shrink_slab:内存压力;
  • _raw_spin_lock:自旋锁竞争;
  • do_sys_openat2:路径解析/文件打开过多。

3. Load 高但 CPU 空闲

load average 统计的是「可运行 + 不可中断睡眠」的任务。CPU 空闲但 load 高,通常是 D 状态(I/O 等待)。

ps -eo state,pid,comm,wchan:32 | awk '$1=="D"'
cat /proc/PID/stack        # root
iostat -xz 1
cat /proc/pressure/io      # PSI
证据结论
大量 D 状态 + %wa 高磁盘/NFS/块层卡住
D 状态 + wchan=...nfs...NFS 服务器不可达
D 状态 + wchan=...rwsem...文件系统/驱动锁
PSI io some 高I/O 压力真实存在

4. 内存增长与 OOM

4.1 分清「缓存」与「泄漏」

free -h
cat /proc/meminfo | egrep 'MemAvailable|Cached|Dirty|SReclaimable|SwapCached'
  • Cached 大不是泄漏,可回收;
  • MemAvailable 才是应用可用;
  • Dirty 持续高:回写跟不上,查 iostat 与 vm.dirty_*。

4.2 进程级

ps -eo pid,rss,vsz,comm --sort=-rss | head
cat /proc/PID/status | egrep 'VmRSS|VmSwap|Threads'
pmap -x PID | tail -1
smem -tk -P PID
  • RSS 包含共享库;PSS 按共享比例分摊,更接近真实;
  • VmSwap 高:被换出,延迟会抖;
  • 线程数持续涨:线程泄漏。

4.3 堆与泄漏

valgrind --leak-check=full --show-leak-kinds=all ./app
# 或
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libasan.so.8 ./app
  • definitely lost:确定泄漏;
  • still reachable:全局缓存/未释放的单例,通常不是 bug;
  • invalid read/write:越界,优先修。

4.4 OOM

dmesg -T | grep -i -E 'oom|killed process'
cat /proc/PID/oom_score

OOM killer 按 oom_score 选受害者;容器里看 cgroup 的 memory.events:

cat /sys/fs/cgroup/<path>/memory.events
# oom 12
# oom_kill 3

5. 磁盘满与 inode 满

df -hT
df -i
du -xhd1 / | sort -h | tail
现象原因处理
df 满但 du 小删除的文件仍被进程持有lsof +L1,重启/截断持有者
df -i 满小文件太多清理缓存/邮件/会话文件
/ 满但 /var 不大日志/容器层//tmpdu -xhd1 /var /tmp
保留块ext4 默认 5% 给 roottune2fs -m 1(慎用)

6. I/O 慢

iostat -xz 1 5
iotop -oPa
sudo biolatency-bpfcc 10 1
cat /proc/pressure/io

读 iostat:

  • r/s、w/s:IOPS;
  • rkB/s、wkB/s:吞吐;
  • r_await、w_await:平均等待(含排队);
  • aqu-sz:平均队列长度;
  • %util:设备忙的比例(NVMe 上参考价值有限)。

常见结论:

  • await 高、aqu-sz 高、%util 高 → 设备饱和;
  • await 高、%util 低 → 单请求慢(可能是网络存储/驱动);
  • 写多、Dirty 高 → 回写瓶颈,调 vm.dirty_ratio 或加带宽。

7. 网络:拒绝、超时、重置

7.1 连接被拒绝(ECONNREFUSED)

ss -lntp | grep :8080
ip route get 10.0.0.5
nft list ruleset | grep -i 8080

顺序:本机有没有监听 → 地址/端口对不对 → 防火墙有没有 drop → 中间有没有 NAT。

7.2 连接超时

ping -c3 host
mtr -rwc 20 host
tcpdump -ni any host and port 8080
  • SYN 发出去没回:网络/防火墙/NAT;
  • SYN 有回、ACK 没回:本机路由/策略路由;
  • 握手成功但无响应:应用没 accept/处理慢。

7.3 连接被重置(ECONNRESET)

  • 对端进程崩溃/重启;
  • 对端 close() 时接收缓冲区还有未读数据(发送 RST);
  • 中间设备(LB/防火墙)超时切断;
  • tcp_abort_on_overflow=1 时 accept 队列溢出。
ss -tin state established '( sport = :8080 )'
dmesg -T | grep -i 'reset\|drop'

7.4 TIME_WAIT / CLOSE_WAIT

ss -s
ss -tan state time-wait | wc -l
ss -tan state close-wait | head
  • TIME_WAIT 多:主动关闭方,正常;端口耗尽才需要 tcp_tw_reuse(谨慎);
  • CLOSE_WAIT 多:应用没 close,是代码 bug,不要调内核参数掩盖。

7.5 DNS

dig +trace example.com
resolvectl query example.com
cat /etc/resolv.conf
  • SERVFAIL:上游/权威问题;
  • NXDOMAIN:名字不存在;
  • 解析慢:dig 看 query time,查 resolv.conf 的 options timeout。

8. 进程卡住/死锁

cat /proc/PID/status | grep State
cat /proc/PID/wchan
cat /proc/PID/stack        # root
gdb -p PID -batch -ex 'thread apply all bt'
  • S + futex_wait:等锁/条件变量;
  • D + 文件系统函数:I/O 卡住;
  • 多线程互相等:gdb 全线程栈,找锁顺序环;
  • T:被 SIGSTOP/调试器暂停。

用户态死锁可用:

valgrind --tool=helgrind ./app
# 或编译时 -fsanitize=thread

9. 崩溃与 core dump

ulimit -c unlimited
cat /proc/sys/kernel/core_pattern
coredumpctl list
coredumpctl gdb PID

gdb 里:

(gdb) bt full
(gdb) info registers
(gdb) frame N
(gdb) p *ptr
(gdb) x/16gx addr

常见信号:

信号含义常见原因
SIGSEGV非法内存访问空指针、越界、UAF、栈溢出
SIGABRTabort()断言失败、free 检测到堆损坏
SIGBUS总线错误mmap 文件被截断、对齐错误
SIGFPE算术异常除零、整数溢出陷阱
SIGILL非法指令二进制/CPU 不匹配、栈被踩

10. 启动失败

阶段现象证据处理
固件黑屏/无引导项efibootmgr重建 ESP/引导项
引导器grub rescue>ls (hd0,gpt2)/重装 grub
内核卡在 Waiting for root devicedmesg 最后几行initramfs 缺驱动/根参数错
initramfs(initramfs) shellblkid修 root=/fstab
systemdemergency.targetjournalctl -xb修失败 unit
登录循环回登录journalctl -u gdm家目录/权限/磁盘满

救援步骤:

  1. GRUB 里加 systemd.unit=rescue.target 或 init=/bin/bash;
  2. mount -o remount,rw /;
  3. 修 /etc/fstab、unit、权限;
  4. reboot -f。

11. 容器问题

现象证据处理
容器起不来docker logs、docker inspect看 State.Error、OOMKilled
容器内看不到进程ls /proc、psPID namespace + --pid=host 对照
端口不通docker port、ss -lntp端口映射/防火墙
磁盘涨docker system df镜像/卷/日志
权限拒绝capsh --print、getcapcapability/seccomp/--privileged
网络慢nsenter -t PID -n ss -tin容器 netns 内看重传

12. 性能回退:二分

git bisect start
git bisect bad HEAD
git bisect good v1.3.0
# 每次构建并跑基准
git bisect run ./bench/check.sh

基准脚本要稳定:

  • 固定 CPU 绑核、预热、重复 3 次取中位数;
  • 输出机器可读(RPS=... P99=...);
  • 阈值判定(例如 RPS 低于基线 5% 算 bad)。

13. 一页速查

症状第一条命令最常被忽略的证据
CPU 高top + perf top%si/%st
load 高ps -eo state,wchanD 状态
内存涨free + smemMemAvailable、PSS
磁盘满df -h; df -ilsof +L1
I/O 慢iostat -xzaqu-sz、await
连不上ss -lntp + tcpdumpSYN 去哪了
连接重置ss -tin + dmesgaccept 队列溢出
进程卡住gdb thread apply all btwchan
崩溃coredumpctl gdb寄存器与栈
启动失败journalctl -xb卡在哪个 target
容器异常docker inspectOOMKilled/State.Error

下一站:A.4 母本覆盖矩阵按原书章节定位正文。