返回文章列表
帮助中心

cpu 使用率一直很高是什么原因?怎么解决?

尔尔
2025-11-24
2个月前
cpu 使用率一直很高是什么原因?怎么解决?

一、引言

“服务卡成 PPT,告警炸群,top 一看 CPU 100%——但到底是哪个线程在‘挖矿’?”

CPU 使用率持续飙高是生产环境中最常见、也最容易误判的问题之一。很多人第一反应是重启服务,结果治标不治本,半夜又被叫醒。更可怕的是,有些“高 CPU”其实是正常行为(比如批处理任务),盲目优化反而破坏业务。

本文将带你用 “侦探式思维 + 工程化手段”,结合两个真实血泪案例,彻底掌握高 CPU 问题的精准定位与根因分析。

二、先问三个灵魂问题

2.1 在动手前,请先确认:是真的异常吗?

批处理、数据导入、定时任务高峰期可能合理高负载。

2.2 是用户态(us)还是内核态(sy)高?

top → 按 1 看各核,看 %us(用户代码) vs %sy(系统调用)
  1. %us 高:应用逻辑问题(死循环、算法低效)
  2. %sy 高:频繁系统调用(如大量 read/write、网络 I/O、锁竞争)

2.3 是单核打满还是多核均匀高?

  1. 单核 100% → 可能存在单线程瓶颈或锁竞争
  2. 多核均高 → 真正的高并发或并行计算

三、高 CPU 排查“五步黄金流程”

第一步:定位罪魁祸首进程

# 实时查看 CPU 占用 Top 进程top -c# 或按 CPU 排序(静态快照)ps aux --sort=-%cpu | head -n 10

记下 PID(进程 ID)

第二步:定位具体线程(关键!)

一个进程可能有上百个线程,必须揪出“真凶”:

# 查看该进程下所有线程 CPU 占用top -H -p <PID># 或用 ps(输出 LWP = 线程 ID)ps -T -p <PID> -o pid,tid,%cpu,comm

记下 TID(线程 ID,十进制)

备注:TID 在 jstack 或 perf 中需转为 十六进制(如 TID=12345 → 0x3039)

第三步:抓取线程堆栈(语言相关)

Java 应用:

# 打印线程堆栈(多次采样对比)jstack <PID> > /tmp/jstack_$(date +%s).log# 快速定位高 CPU 线程printf "%x\n" <TID>  # 转十六进制grep -A50 "nid=0x<HEX_TID>" /tmp/jstack_*.log

Go 应用:

# 发送 SIGUSR2 触发 pprof(需程序支持)kill -SIGUSR2 <PID># 或直接访问 pprof 接口(如果暴露了)go tool pprof http://localhost:6060/debug/pprof/profile

C/C++/Python 等:

# 使用 perf(Linux 性能神器)perf record -g -p <PID> sleep 10perf report

第四步:分析热点函数(性能剖析)

# 全局 CPU 热点(无需提前知道 PID)perf top# 或生成火焰图(终极可视化)git clone https://github.com/brendangregg/FlameGraphperf record -F 99 -a -g -- sleep 30perf script | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl > cpu.svg

火焰图一眼看出:哪个函数在“燃烧”CPU!

第五步:关联业务日志 & 监控

  1. 高 CPU 时间点是否对应特定接口调用?
  2. 是否有异常流量(爬虫、DDoS)?
  3. GC 日志是否频繁 Full GC(Java)?

四、真实案例剖析:两个“CPU 杀手”

案例一:Java 服务 CPU 100% —— 正则表达式“灾难性回溯”

现象:

  1. 用户反馈登录接口超时
  2. 监控显示 CPU 持续 95%+
  3. top 显示 Java 进程占满单核

排查过程:

  1. top -H -p→ 找到 TID=28456
  2. printf "%x\n" 28456 → 得到 0x6f28
  3. jstack| grep -A50 "nid=0x6f28" → 堆栈显示:
"http-nio-8080-exec-5" #28456   at java.util.regex.Pattern$Loop.match(...)   at java.util.regex.Pattern$GroupTail.match(...)   at com.example.UserValidator.validateEmail(...)

定位到代码:

// 危险正则!对恶意输入会指数级回溯String EMAIL_REGEX = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";email.matches(EMAIL_REGEX); // 攻击者提交 "a@aaaaaaaaaaaaaaaaaaaaaaa!"

解决方案:

  1. 替换为安全的邮箱验证库(如 Apache Commons Validator)
  2. 或加长度限制 + 超时控制 教训:正则表达式是隐形的 CPU 杀手! 尤其在用户输入场景。

案例二:Go 服务 CPU 飙升 —— map 并发写导致锁竞争

现象:

  1. API 延迟从 10ms 暴涨至 2s+
  2. top 显示多核 CPU 均高(70%+)
  3. 无明显错误日志

排查过程:

  1. 访问 http://service:6060/debug/pprof/profile?seconds=10 下载 profile
  2. 用 go tool pprof 分析:
go tool pprof cpu.prof(pprof) top10

输出显示:

42.3%  runtime.mapassign_faststr28.1%  runtime.mapaccess2_faststr

代码审查发现:

var cache = make(map[string]interface{}) // 全局 map,无锁!func Get(key string) interface{} {    return cache[key] // 并发读写!}

Go 的 map 并发写会触发内部锁竞争,导致大量 goroutine 阻塞在 runtime 层

解决方案:

  1. 改用 sync.Map
  2. 或加 RWMutex 保护 效果:CPU 从 70% 降至 15%,延迟恢复 10ms!

五、预防建议:让高 CPU 无处藏身

5.1 上线前必做:

  1. 压测 + CPU 火焰图基线对比
  2. 敏感操作(正则、JSON 解析)加超时

5.2 监控告警:

加上一下的几个监控告警,可以做到及时提前处理

  1. 单核 CPU > 80% 持续 5 分钟
  2. 线程数突增
  3. GC 时间占比 > 10%

5.3 架构层面:

  1. 关键服务开启 pprof / JMX
  2. 使用 eBPF 工具(如 Pixie、Parca)实现无侵入 profiling


本文内容仅供参考,不构成任何专业建议。使用本文提供的信息时,请自行判断并承担相应风险。

分享文章
合作伙伴

本站所有广告均是第三方投放,详情请查询本站用户协议