线上 CPU 100% 是典型的性能故障,会直接导致服务响应延迟、超时甚至不可用,处理核心原则是先应急止损,再定位根因,最后彻底解决并预防。以下是分阶段的详细方案,涵盖 Linux 环境下的通用操作及 Java、Go 等主流语言的针对性工具。
应急阶段的目标是快速降低 CPU 负载,保障业务不中断,无需等待根因定位,核心操作围绕 “分流、扩容、隔离” 展开:
首先通过监控平台(如 Prometheus、Grafana、Zabbix)确认:
- 是单个实例还是集群批量CPU 高?(单个可能是实例异常,批量可能是代码 / 配置 / 流量问题)
- 高 CPU 是否伴随流量突增(如秒杀、爬虫)、新发布版本(灰度 / 全量后出现)?
应急后需精准定位根因,避免问题复现。定位逻辑遵循 **“进程→线程→代码 / 任务”** 的递进思路,依赖 Linux 系统命令 + 应用层工具。
Linux 环境下通过命令行快速锁定 “罪魁祸首”,核心工具:top
、ps
、pidstat
。
执行top
命令(按P
键按 CPU 使用率排序),重点关注两列:
- %us:用户态 CPU 占比(若高,说明应用代码消耗 CPU 多,如死循环、计算密集);
- %sy:内核态 CPU 占比(若高,说明系统调用频繁,如频繁 IO、线程切换)。
示例输出中,PID=1234 的进程 CPU 使用率 100%,用户态占比 98%,说明是应用代码问题:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1234 appuser 20 0 20.5g 8.3g 1.2g R 100.0 21.5 5:30.12 java
通过top -Hp <PID>
查看进程内线程的 CPU 占用(按P
排序),获取高 CPU 线程的TID(线程 ID,十进制):
示例中,TID=1245 的线程 CPU 占比 99%,是核心线程。
Java 的jstack
、Go 的pprof
等工具需用十六进制线程 ID匹配栈信息,执行以下命令转换(以 TID=1245 为例):
根据应用语言选择工具,核心是获取线程的调用栈信息,定位到具体代码行。
依赖 JDK 自带工具:jps
、jstack
、jstat
(排查 GC 相关 CPU 高)。
用jstack
导出目标进程的全量线程栈,并搜索十六进制 TID(如 4db)对应的线程:
jstack 1234 > java_stack.log
grep -A 20 "nid=0x4db" java_stack.log
重点关注线程状态(RUNNABLE
最可能导致 CPU 高)和调用栈:
- 若线程状态为
RUNNABLE
,且调用栈反复出现某段代码(如com.example.service.UserService.calculate()
),说明该方法存在死循环或高频计算;
- 若线程状态为
WAITING
/BLOCKED
但 CPU 高,需排查是否有自旋锁(如AtomicInteger
循环 CAS)或频繁 GC。
示例栈日志(死循环特征):
"Thread-1" #10 prio=5 os_prio=0 cpu=99999ms elapsed=10.0s tid=0x00007f8a1c004800 nid=0x4db runnable [0x00007f8a08f9e000]
java.lang.Thread.State: RUNNABLE
at com.example.service.UserService.calculate(UserService.java:45) # 反复执行该方法
at com.example.service.UserService.process(UserService.java:30)
at java.lang.Thread.run(Thread.java:748)
若top
中 Java 进程 % us 高,但无明显死循环线程,需检查 GC 情况(频繁 GC 会导致 CPU 占用飙升):
重点关注:
- FGC(Full GC)次数:若每秒多次 FGC,且
FGCT
(Full GC 耗时)持续增加,说明内存泄漏(对象无法回收,JVM 反复 GC);
- YGCT(Young GC 耗时):若 YGCT 过高,可能是对象创建过于频繁(如循环内 new 对象)。
依赖 Go 自带工具pprof
和系统工具gdb
,核心定位goroutine 泄漏或高频计算。
- 方式 1:代码中嵌入 pprof(推荐线上):
import _ "net/http/pprof"
func main() {
go func() {
log.Println(http.ListenAndServe("0.0.0.0:6060", nil))
}()
}
- 方式 2:离线导出 profile(无端口时):
go tool pprof -seconds 30 http://localhost:6060/debug/pprof/profile?seconds=30
进入 pprof 交互界面后,执行top
查看高 CPU 的函数:
(pprof) top 10 # 显示前10个高CPU函数
Showing nodes accounting for 990ms, 99% of 1000ms total
Dropped 10 nodes (cum <= 5ms)
flat flat% sum% cum cum%
500ms 50.00% 50.00% 500ms 50.00% com.example/calc.Sum
490ms 49.00% 99.00% 490ms 49.00% com.example/loop.Run # 高频循环函数
执行list <函数名>
查看具体代码行:
(pprof) list Run
Total: 1s
ROUTINE ======================== com.example/loop.Run in /app/loop.go
490ms 490ms (flat, cum) 49.00% of Total
. . 1:package loop
. . 2:
. . 3:func Run() {
. . 4: for { // 死循环!
490ms 490ms 5: i := 0
. . 6: i++
. . 7: }
. . 8:}
依赖工具py-spy
(非侵入式,适合线上)或cProfile
(侵入式,适合线下)。
pip install py-spy
py-spy record -o python_profile.svg --pid 1234
打开python_profile.svg
,通过火焰图直观看到高 CPU 的函数调用链(火焰越高,CPU 占用越高)。
定位到疑似代码后,需结合业务日志和监控数据验证:
- 查看应用日志(如
tail -f /var/log/app.log
),确认高 CPU 线程对应的业务场景(如某个接口、定时任务);
- 对比故障前后的监控(如接口 QPS、耗时、线程数),确认是否与定位的代码逻辑关联(如 QPS 突增导致高频调用某计算函数);
- 线下复现:在测试环境模拟相同参数(如输入导致死循环的异常数据),验证 CPU 是否飙升,确认根因。
根据定位结果,针对性解决问题,常见根因及方案如下:
故障解决后需复盘,形成闭环,核心动作:
- 新增 CPU 相关告警:如 “单实例 CPU>90% 持续 30 秒”“集群 CPU 平均 > 80%”“Java 进程 FGC>5 次 / 分钟”;
- 补充业务监控:如接口 QPS、耗时、线程数、goroutine 数,提前发现流量或代码异常。
- 灰度发布:新代码先小流量(如 10%)验证,观察 CPU 等指标,无异常再全量;
- 压力测试:上线前用 JMH、JMeter 模拟高流量,验证 CPU 是否存在瓶颈;
- 代码审查:重点检查死循环、锁使用、内存申请、第三方调用超时逻辑。
- 记录本次故障的 “时间线、根因、解决方案、优化措施”;
- 整理《线上 CPU 100% 应急手册》,明确不同角色的职责(如运维负责应急止损,开发负责定位根因)。
线上 CPU 100% 处理的核心是 “快止损、准定位、彻解决”:应急阶段优先保障业务可用,定位阶段通过 “系统命令 + 应用工具” 层层拆解,解决阶段针对性修复代码或配置,最后通过复盘建立预防机制。熟练掌握top
、jstack
、pprof
等工具,可大幅提升定位效率,减少故障影响范围。