线上 CPU 100% 是典型的性能故障,会直接导致服务响应延迟、超时甚至不可用,处理核心原则是先应急止损,再定位根因,最后彻底解决并预防。以下是分阶段的详细方案,涵盖 Linux 环境下的通用操作及 Java、Go 等主流语言的针对性工具。
一、应急处理:优先恢复业务可用
应急阶段的目标是快速降低 CPU 负载,保障业务不中断,无需等待根因定位,核心操作围绕 “分流、扩容、隔离” 展开:
1. 快速判断故障范围
首先通过监控平台(如 Prometheus、Grafana、Zabbix)确认:
- 是单个实例还是集群批量CPU 高?(单个可能是实例异常,批量可能是代码 / 配置 / 流量问题)
- 高 CPU 是否伴随流量突增(如秒杀、爬虫)、新发布版本(灰度 / 全量后出现)?
2. 核心应急操作(按优先级排序)
| 操作场景 | 具体步骤 | 注意事项 |
|---|---|---|
| 单个实例 CPU 高 | 1. 临时下线该实例(通过网关 / 负载均衡剔除,如 Nginx、K8s Service); 2. 重启实例( systemctl restart 服务名或容器重启);3. 重启后观察 CPU 是否恢复,若恢复则保留日志待后续分析。 |
避免直接重启集群,仅下线异常实例,防止业务中断。 |
| 集群批量 CPU 高 | 1. 紧急扩容(增加实例数,如 K8s HPA 自动扩容或手动扩缩容); 2. 流量限流 / 降级(通过网关限制过载接口的 QPS,如 Sentinel、Nginx 限流); 3. 若刚发布新版本,立即回滚到上一稳定版本。 |
扩容仅临时分担负载,需后续定位根因;回滚前确认新版本是故障触发点。 |
| 流量突增导致 CPU 高 | 1. 识别突增流量来源(如爬虫、恶意请求),通过防火墙 / 网关拦截 IP; 2. 对非核心接口降级(返回默认值,如 “服务繁忙”),优先保障核心业务。 |
需区分 “正常流量峰值” 和 “异常流量”,避免误拦截正常用户。 |
二、故障定位:从系统层到应用层拆解
应急后需精准定位根因,避免问题复现。定位逻辑遵循 **“进程→线程→代码 / 任务”** 的递进思路,依赖 Linux 系统命令 + 应用层工具。
阶段 1:系统层定位 —— 找到高 CPU 的进程与线程
Linux 环境下通过命令行快速锁定 “罪魁祸首”,核心工具:
top、ps、pidstat。步骤 1:找到高 CPU 的进程(PID)
执行
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
步骤 2:找到进程内高 CPU 的线程(TID)
通过
top -Hp <PID>查看进程内线程的 CPU 占用(按P排序),获取高 CPU 线程的TID(线程 ID,十进制):top -Hp 1234 # 查看PID=1234的进程内线程
示例中,TID=1245 的线程 CPU 占比 99%,是核心线程。
步骤 3:将 TID 转为十六进制(适配 Java 等工具)
Java 的
jstack、Go 的pprof等工具需用十六进制线程 ID匹配栈信息,执行以下命令转换(以 TID=1245 为例):printf "%x\n" 1245 # 输出:4db(十六进制,小写)
阶段 2:应用层定位 —— 分析线程栈与业务逻辑
根据应用语言选择工具,核心是获取线程的调用栈信息,定位到具体代码行。
场景 1:Java 应用(最常见)
依赖 JDK 自带工具:
jps、jstack、jstat(排查 GC 相关 CPU 高)。1. 导出线程栈日志
用
jstack导出目标进程的全量线程栈,并搜索十六进制 TID(如 4db)对应的线程:# 导出栈信息到文件(避免终端刷屏)
jstack 1234 > java_stack.log
# 搜索高CPU线程的栈信息(TID=4db,注意小写)
grep -A 20 "nid=0x4db" java_stack.log
2. 分析栈日志核心信息
重点关注线程状态(
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)
3. 排查是否为 GC 导致 CPU 高
若
top中 Java 进程 % us 高,但无明显死循环线程,需检查 GC 情况(频繁 GC 会导致 CPU 占用飙升):# 每1秒输出一次GC统计(持续观察)
jstat -gc 1234 1000
重点关注:
- FGC(Full GC)次数:若每秒多次 FGC,且
FGCT(Full GC 耗时)持续增加,说明内存泄漏(对象无法回收,JVM 反复 GC); - YGCT(Young GC 耗时):若 YGCT 过高,可能是对象创建过于频繁(如循环内 new 对象)。
场景 2:Go 应用
依赖 Go 自带工具
pprof和系统工具gdb,核心定位goroutine 泄漏或高频计算。1. 开启 pprof 监控(两种方式)
- 方式 1:代码中嵌入 pprof(推荐线上):
import _ "net/http/pprof" // 导入后自动注册pprof接口 func main() { go func() { log.Println(http.ListenAndServe("0.0.0.0:6060", nil)) // 暴露pprof端口 }() // 业务代码 } - 方式 2:离线导出 profile(无端口时):
go tool pprof -seconds 30 http://localhost:6060/debug/pprof/profile?seconds=30 # 30秒内采样CPU使用,生成profile文件
2. 分析 CPU 占用热点
进入 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:}
场景 3:Python 应用
依赖工具
py-spy(非侵入式,适合线上)或cProfile(侵入式,适合线下)。1. 用 py-spy 查看 CPU 热点
# 安装py-spy(无需修改代码,直接attach进程)
pip install py-spy
# 查看PID=1234的Python进程CPU使用,输出到文件
py-spy record -o python_profile.svg --pid 1234
打开
python_profile.svg,通过火焰图直观看到高 CPU 的函数调用链(火焰越高,CPU 占用越高)。阶段 3:根因验证与确认
定位到疑似代码后,需结合业务日志和监控数据验证:
- 查看应用日志(如
tail -f /var/log/app.log),确认高 CPU 线程对应的业务场景(如某个接口、定时任务); - 对比故障前后的监控(如接口 QPS、耗时、线程数),确认是否与定位的代码逻辑关联(如 QPS 突增导致高频调用某计算函数);
- 线下复现:在测试环境模拟相同参数(如输入导致死循环的异常数据),验证 CPU 是否飙升,确认根因。
三、根本解决:针对根因修复
根据定位结果,针对性解决问题,常见根因及方案如下:
| 常见根因 | 解决方案 |
|---|---|
| 死循环(代码逻辑错误) | 1. 修复循环条件(如将while(true)改为有限条件,处理异常边界值);2. 代码审查时重点检查循环、递归逻辑。 |
| 频繁 GC / 内存泄漏 | 1. 排查内存泄漏点(如未关闭的连接、静态集合无限添加); 2. 优化对象创建(如使用对象池); 3. 调整 JVM 参数(如增大堆内存 -Xmx)。 |
| 资源竞争(锁冲突) | 1. 减小锁粒度(如用ConcurrentHashMap代替HashMap+ReentrantLock);2. 用无锁结构(如 Atomic类);3. 避免同步阻塞(如异步化处理)。 |
| 计算密集型任务突增 | 1. 拆分任务(如分布式计算); 2. 异步化处理(如用消息队列削峰); 3. 扩容计算节点,单独部署计算任务。 |
| 第三方服务 / 接口超时 | 1. 增加超时时间(避免线程长期阻塞); 2. 降级熔断(如 Hystrix,超时后返回默认值); 3. 替换不稳定的第三方服务。 |
四、复盘与预防:避免再次发生
故障解决后需复盘,形成闭环,核心动作:
1. 完善监控与告警
- 新增 CPU 相关告警:如 “单实例 CPU>90% 持续 30 秒”“集群 CPU 平均 > 80%”“Java 进程 FGC>5 次 / 分钟”;
- 补充业务监控:如接口 QPS、耗时、线程数、goroutine 数,提前发现流量或代码异常。
2. 优化发布与测试流程
- 灰度发布:新代码先小流量(如 10%)验证,观察 CPU 等指标,无异常再全量;
- 压力测试:上线前用 JMH、JMeter 模拟高流量,验证 CPU 是否存在瓶颈;
- 代码审查:重点检查死循环、锁使用、内存申请、第三方调用超时逻辑。
3. 文档沉淀
- 记录本次故障的 “时间线、根因、解决方案、优化措施”;
- 整理《线上 CPU 100% 应急手册》,明确不同角色的职责(如运维负责应急止损,开发负责定位根因)。
总结
线上 CPU 100% 处理的核心是 “快止损、准定位、彻解决”:应急阶段优先保障业务可用,定位阶段通过 “系统命令 + 应用工具” 层层拆解,解决阶段针对性修复代码或配置,最后通过复盘建立预防机制。熟练掌握
top、jstack、pprof等工具,可大幅提升定位效率,减少故障影响范围。