工具会用,不等于能定位问题。很多人手里攥着 top、iostat、sar,遇到「系统慢」还是乱敲一通,看哪儿都像瓶颈。真正的高手靠的是方法论------一套「怎么切入、按什么顺序查、如何验证」的框架。这一篇讲透性能分析的两大方法论(USE 和 RED)、火焰图的原理与实战,帮你从「工具体力活」升级到「思路技术活」。
一、为什么需要方法论
先看一个典型的反面教材:
程序员小李接到告警「接口超时」。他登录机器,敲了 top,
看到 CPU 10%,觉得没问题;又看 free,内存也够;
最后猜是「网络抖动」,重启了服务。问题暂时好了,
第二天又来了。
没有方法论的排查,就是靠猜和重启。有方法论的人会这样走:
- 先明确「慢」的定义:是哪个接口、P99 多少、从何时开始。
- 按 USE 检查物理资源:CPU、内存、磁盘、网络有没有饱和。
- 按 RED 检查服务本身:请求量、错误率、延迟。
- 用
perf+ 火焰图钻到代码热点。 - 找到根因,改,然后用数据验证。
方法论的价值在于:保证覆盖、减少遗漏、可复现、可解释。下面两个框架是业界公认的两把尺子。
二、USE 方法:面向资源
USE = Utilization(使用率)、Saturation(饱和度)、Errors(错误) ,由 Brendan Gregg 提出,专门用来系统性地检查每一类资源。
对系统里的每种资源,依次问三个问题:
- 使用率:资源有多忙?(如 CPU 80% 忙)
- 饱和度:有多少工作在排队?(如运行队列长度)
- 错误:有报错吗?(如网卡丢包、磁盘错)
一张表覆盖所有资源
| 资源 | 使用率 | 饱和度 | 错误 |
|---|---|---|---|
| CPU | mpstat 的 %usr+%sys |
vmstat 的 r 列 |
dmesg 里 MCE |
| 内存 | free 的 used |
vmstat 的 si/so、OOM |
dmesg 的 OOM |
| 磁盘 | iostat 的 %util |
iostat 的 aqu-sz |
smartctl、dmesg |
| 网络 | sar -n DEV 带宽 |
ss 的 Recv-Q、重传 |
netstat -s 错误 |
| 文件描述符 | ls /proc/<pid>/fd | wc -l |
--- | Too many open files |
实战:一步一步走 USE
# 1. CPU 使用率 + 饱和度
mpstat -P ALL 1 3 # 使用率
vmstat 1 3 # r 列 = 运行队列饱和度
# 2. 内存使用率 + 饱和度
free -h
vmstat 1 3 # si/so 恒定,饱和
grep -E "Dirty|Writeback" /proc/meminfo
# 3. 磁盘使用率 + 饱和度
iostat -dxm 1 3 # %util / aqu-sz
# 4. 网络使用率 + 错误
sar -n DEV 1 3
sar -n EDEV 1 3
# 5. 全局错误
dmesg -T | tail -50
USE 的排查顺序:先找利用率高的、饱和的、有错误的资源。 三者中任何一项异常,就是线索。
⚠️ USE 的盲区
USE 只看物理资源。如果所有资源都正常,但应用还是慢,那问题在「应用层」------锁竞争、GC、慢查询、算法复杂度。这时要换 RED。
三、RED 方法:面向服务
RED = Rate(请求速率)、Errors(错误率)、Duration(延迟)。它从「服务/微服务」的视角看问题,特别适合排查「接口慢」这类应用层问题。
三个维度
| 指标 | 含义 | 工具 |
|---|---|---|
| Rate | 每秒请求数(QPS) | 应用埋点、Nginx 日志、APM |
| Errors | 每秒错误数 / 错误率 | 应用埋点、日志 |
| Duration | 请求耗时分布(P50/P95/P99) | APM、链路追踪 |
关键:Duration 要看分位数,不能只看平均值。 平均 50ms 的接口,P99 可能是 3 秒------平均值掩盖了尾部延迟,而用户体验由尾部决定。
与 USE 的配合
- 服务层异常 → 用 RED 定位是哪个服务、哪个接口。
- 服务层正常但机器慢 → 用 USE 定位是哪个资源。
两者是互补的:RED 告诉你「哪里慢」,USE 告诉你「为什么慢」。
示例:用日志算 RED
# Nginx 日志里统计每分钟请求数(Rate)
awk '{print $4}' access.log | cut -c14-18 | sort | uniq -c
# 统计 5xx 错误率(Errors)
awk '$9 ~ /^5/ {c++} END {print c}' access.log
# 统计耗时分布(Duration,需日志带 $request_time)
awk '{print $NF}' access.log | sort -n | awk '{a[NR]=$1} END {print "P50:", a[int(NR*0.5)], "P99:", a[int(NR*0.99)]}'
四、性能分析的通用三板斧
无论用哪套方法论,落地都是这三步:
| 阶段 | 做什么 | 关键动作 |
|---|---|---|
| 观察 | 收集现象和数据 | 明确「慢」的定义,采集指标 |
| 定位 | 缩小范围,找到根因 | 自上而下 or 自下而上,逐层排除 |
| 验证 | 证明改动有效 | 改前改后对比数据 |
观察:先定义「慢」
模糊的「系统慢」没法查。要问清楚:
- 是所有操作慢 ,还是某个接口慢?
- 从什么时候开始?之前有变更吗?(部署、配置、流量)
- 慢到什么程度?P99 多少?
- 能复现吗?
这一步偷懒,后面全是白费。
定位:两种切入路线
- 自上而下 :从应用/服务视角(RED)往下,先看哪个服务慢,再看资源。
- 适合:知道具体哪个接口/服务出问题。
- 自下而上 :从物理资源(USE)往上,先看哪个资源饱和,再看谁用的。
- 适合:只知道「机器整体变慢」。
验证:用数据说话
改完必须验证,不能凭感觉:
# 改前记录基线
mpstat -P ALL 1 5 > before.txt
# 做优化
# 改后对比
mpstat -P ALL 1 5 > after.txt
diff before.txt after.txt
没有验证的优化,可能只是猜对了,也可能是问题自己消失了。
五、火焰图:一把看穿 CPU 热点
前面讲的是「方法」,火焰图是「利器」。它把成千上万次采样数据可视化成一棵树,让你一眼看出哪个函数、哪条调用链在吃 CPU。
火焰图长什么样
一张标准火焰图:
┌──────────────────────────────┐
│ main (100%) │ <- 顶层:入口
├──────────────────┬───────────┤
│ process_data │ parse() │
│ (65%) │ (35%) │
├────────┬─────────┤
│ sort() │ io() │
│ (40%) │ (25%) │
└────────┴─────────┘
怎么读火焰图(核心规则)
- 每一层是一个函数,宽度代表它在采样中占的比例(即 CPU 时间占比)。
- 越宽说明占用 CPU 越多,是优化重点。
- 从下往上是调用关系:下面的函数调用了上面的函数。
- 颜色不重要(通常是随机暖色),只是区分函数。
- 看「平顶」:一个宽宽的、没有明显下层的函数,说明时间花在它自己身上(而非它调用的子函数),是热点。
找最宽的、最上面那个「平坦」的方块,就是元凶。
六、perf + 火焰图实战
安装依赖
sudo apt install linux-tools-common linux-tools-$(uname -r) # Ubuntu
sudo dnf install perf # Rocky/CentOS
火焰图需要 Brendan Gregg 的 FlameGraph 脚本:
git clone https://github.com/brendangregg/FlameGraph.git
cd FlameGraph
四步生成火焰图
# 1. 采样 30 秒,-g 记录调用栈,-a 全系统,-F 99 采样频率 99Hz
sudo perf record -F 99 -a -g -- sleep 30
# 2. 导出采样数据为文本
sudo perf script > out.perf
# 3. 折叠调用栈
./stackcollapse-perf.pl out.perf > out.folded
# 4. 生成 SVG 火焰图
./flamegraph.pl out.folded > flame.svg
打开 flame.svg,浏览器里就能交互式查看(支持点击放大、搜索函数)。
针对某个进程采样
# 只采样 PID 1234 的进程
sudo perf record -F 99 -p 1234 -g -- sleep 30
用 perf top 快速看热点
来不及做完整火焰图时,perf top 实时看函数级热点,够用:
sudo perf top -p 1234
Overhead Symbol
42.30% [.] handle_request
18.10% [.] json_encode
9.50% [k] copy_user_generic_string
⚠️ 常见坑
- 采样频率过高 :
-F 99是常用值,调到 1000 会明显增加开销。 - sudo 权限 :
perf record通常需要 root 或CAP_PERFMON。 kernel.perf_event_paranoid限制 :设成 2 时普通用户不能采样,需sudo sysctl kernel.perf_event_paranoid=1。- 符号丢失:看不到函数名(全是地址),说明二进制没带符号表或没装 debuginfo。
- Java/Python 应用 :默认看不到 JIT/解释器内部函数,需要
perf-map-agent(Java)或py-spy(Python)。
七、off-CPU 火焰图:抓住「不在跑」的时间
标准火焰图(on-CPU)只展示CPU 在忙什么 。但很多性能问题不是「CPU 忙」,而是进程在等------等锁、等 IO、等网络。这些时间 CPU 是空闲的,on-CPU 火焰图完全看不到。
off-CPU 火焰图记录进程「离开 CPU」的时段和原因,用来分析阻塞和延迟:
# 需要 eBPF/bcc 工具
sudo offcputime -df -p 1234 30 > offcpu.folded
./flamegraph.pl --color=io --title="Off-CPU" offcpu.folded > offcpu.svg
它能回答「进程为什么卡住 3 秒」------因为等某把锁,或等磁盘返回。on-CPU 看热点,off-CPU 看阻塞,两者结合才是完整的性能画像。
八、⚠️ 方法论层面的常见错误
- 上来就测,不定基线。不知道「正常」长什么样,就无法判断「异常」。
- 只修症状不找根因。重启服务、加机器能暂时缓解,但根因没解决必然复发。
- 迷信平均值。CPU 平均 30% 但单核 100%、延迟平均 50ms 但 P99 3s------平均值是最大的谎话。
- 一次改多个变量 。同时改了三处,出问题不知道是哪处。一次只改一个,验证后再改下一个。
- 忽略流量模式。突增流量、定时任务(备份、批处理)会和业务抢资源,排查时要把时间线对齐。
- 不做压测就下结论 。生产不敢压,就在测试环境用
wrk/jmeter/fio复现,有基线才好对比。 - 把方法论当死板流程。USE/RED 是「检查清单」,不是「必须按序执行」,凭线索灵活跳转。
九、原理探究:采样 vs 埋点,火焰图为何有效
采样(Sampling)的原理
perf 不记录每一次函数调用(那样开销太大),而是每隔固定时间触发一次中断,记下此刻 CPU 正在执行哪个函数。采样几十秒得到几万个样本,统计每个函数出现的频率,就约等于它的 CPU 占比。
采样模式的好处:
- 开销低(1%~5%),可以对着生产环境跑。
- 不侵入:不需要改代码、重新编译。
- 统计有效:样本足够多时,频率收敛到真实的 CPU 时间占比。
代价 :它是概率性的,短命函数可能采不到。要抓偶发问题,需要拉长采样时间或提高频率。
为什么火焰图这么有效
火焰图把「调用栈 + 时间占比」编码成「横轴宽度 + 纵向层级」,利用了人眼的两个强项:
- 对宽度的直觉:一眼看出哪个最宽。
- 对层级的直觉:一眼看出调用链。
它把「几万行采样数据」压缩成「一张可以直接看结论的图」 ,这就是它的威力。传统 profiler 给的是排序列表,火焰图给的是结构------谁调用了谁、瓶颈在哪条路径上,一目了然。
从现象到根因的完整链路
把整篇串起来,一个成熟的性能分析流程是:
① 观察:用 RED 确认哪个服务/接口慢,用 USE 扫一遍资源
↓
② 缩范围:排除资源饱和 → 问题在应用层
↓
③ 定位:perf 采样 + 火焰图,找到热点函数
↓
④ 深挖:on-CPU 看计算热点,off-CPU 看阻塞等待
↓
⑤ 改:优化算法/加锁策略/改配置/扩资源(一次一个变量)
↓
⑥ 验证:改前改后数据对比,确认有效
工具是「术」,方法论是「道」。 工具会更新换代,top 变 htop、netstat 变 ss;但「先观察、再定位、后验证;资源用 USE、服务用 RED;热点看火焰图、阻塞看 off-CPU」这套思路,十年都不会过时。
十、一页速查
| 场景 | 抓手 |
|---|---|
| 不知道从哪查 | 先 USE 扫资源,再 RED 看服务 |
| 机器整体慢 | 自下而上:USE |
| 某接口慢 | 自上而下:RED |
| CPU 热点 | perf record + 火焰图 |
| 进程卡住 | off-CPU 火焰图 |
| 想证明优化有效 | 改前改后基线对比 |
| 最难的一步 | 明确定义「慢」,并对齐时间线 |
性能分析的正确姿势是:先想清楚问题的定义,再用方法论保证覆盖,最后用采样工具定位到代码。工具会用的很多,思路清醒的很少------这篇的目的,就是让你成为后者。