图 1:本文封面,放大镜下的三条横线就是三级收窄。
先说明属性:这是一篇经典题解析,题目出自本地历史面试资料,与任何公司的当前招聘无关。它是一道纯实战题:没有知识点可背,面试官想听的是一条可操作的定位路径——从整机到一个函数,每一步用什么工具、看什么字段。
想象面试现场:「监控报警,某台机器 CPU 打满。你现在 SSH 上去了,接下来做什么?」
核心结论一句话
三级收窄:先找进程,再找线程,最后找代码。顺序不能反——直接上剖析工具等于在整台机器里大海捞针;先锁进程再锁线程,最后那一步的工具随便换(Java 用 jstack,通用用 perf 类采样),路径本身与语言无关。
第一级:找进程。
top 按 CPU 排序(按大写 P 或默认即按 CPU 降序),锁定占用最高的进程号。顺手看两眼的还有:load average 与 CPU 核数的关系(8 核机器 load 30 才是排队,单核机器 load 4 就已经饱和);us 与 sy 的占比——us 高是应用代码在烧,sy 高是内核态在烧(系统调用、上下文切换),两者指向完全不同的排查分支。
第二级:找线程。
top 加 -H 选项切到线程视图(man 页原话:显示各个线程而不是按进程汇总),或交互界面里按 H 切换。同一进程里最热的那个线程现形,记下它的线程号 TID。关键动作:把十进制 TID 转成十六进制——因为下一步的线程转储里,线程标识是十六进制的。printf '%x\n' 12345 一行搞定。
第三级:找代码。
Java 进程用 jstack 抓线程转储,按十六进制线程号(转储里的 nid 字段)捞出对应栈帧。抓一次可能是偶然,隔几秒连抓三次,栈顶反复出现在同一个方法上,那就是热点。如果栈帧每次都不一样、或根本不是 Java 进程,换采样剖析:perf top 直接按函数实时统计 CPU 占比,哪一行符号常年霸榜,热点就是它。两类最常见的结论形态:一是死循环与正则灾难反溯——栈顶永远停在同一个 match 或同一个循环体;二是序列化与 JSON 解析热点——栈顶大片落在 parse、encode 相关帧上,这在推理网关里尤其常见。
一段演示代码:热点的结论形态长什么样。
下面这段脚本故意制造一个热点函数,用计时对比演示「最后一级的结论形态」。用 Node.js 22.15.0 直接运行(node hotspot.js)即可:
'use strict';
// 两个候选函数:一个快,一个是故意制造的热点
function tokenizeFast(text) {
return text.split(/\s+/).length;
}
function jsonParseHot(text) {
// 灾难写法:对每个 token 都做一次 JSON 序列化 + 解析往返
let acc = 0;
for (const w of text.split(/\s+/)) {
acc += JSON.parse(JSON.stringify({ w })).w.length;
}
return acc;
}
const text = 'token '.repeat(2000);
function bench(name, fn) { // 各跑两百次,看 CPU 时间花在哪
const start = process.hrtime.bigint();
let acc = 0;
for (let i = 0; i < 200; i++) acc += fn(text);
const ms = Number(process.hrtime.bigint() - start) / 1e6;
console.log(`${name}: ${ms.toFixed(1)}ms`);
return ms;
}
const fast = bench('tokenizeFast', tokenizeFast);
const hot = bench('jsonParseHot', jsonParseHot);
console.log(`热点占比:${((hot / (fast + hot)) * 100).toFixed(1)}% 的时间花在 jsonParseHot`);
实测输出(耗时随机器波动,占比量级稳定):
tokenizeFast: 7.6ms
jsonParseHot: 71.4ms
热点占比:90.4% 的时间花在 jsonParseHot
线上 perf top 或火焰图给出的就是这种形态:一列函数名加占比,90% 集中在一个不该热的地方。看到它,修改方向就是显然的——把逐 token 的序列化往返改成一次整包处理。
图 2:三级收窄路径与两类常见结论。
可能的追问与易错点。
「如果不是 us 高而是 sy 高呢?」方向整个换掉:sy 高先怀疑系统调用风暴与上下文切换——strace 看调用频率、vmstat 看 cs 列,频繁的小读写、锁争抢导致的调度抖动都是典型。「CPU 不高但服务就是慢呢?」那大概率不在 CPU:看磁盘 iostat、看网络、看锁等待,别把 CPU 排查模板硬套上去。易错点有三:一是抓到一次栈就下结论——单次快照可能是噪声,连抓三次看重复模式才作数;二是忘了十进制转十六进制,拿着十进制 TID 在转储里搜半天搜不到;三是只盯自己的服务进程——打满 CPU 的可能是同机的日志采集、监控 Agent,第一级 top 就是干这个排除的。
回到面试现场,建议的答题节奏:先给三级路径总纲,再逐级说工具与关键字段(CPU 排序、-H、TID 转十六进制、nid),然后补 us/sy 分支判断展示你知道排查有岔路,最后落一个真实场景——推理服务的 tokenize 与 JSON 解析热点,用「先找进程、再找线程、最后找代码」收束。这条路径Java、Go、Python 通用,变的只是第三步的工具。
你排查过最奇葩的 CPU 打满是什么原因?欢迎聊聊。