服务器CPU飙升定位

技术背景:理解CPU使用率的本质

CPU使用率的构成

很多人认为CPU使用率就是一个简单的百分比,但实际上Linux系统的CPU使用率包含多个维度:

# 使用top命令查看CPU详细信息
%Cpu(s):  25.3 us,  5.2 sy,  0.0 ni, 68.1 id,  1.2 wa,  0.0 hi,  0.2 si,  0.0 st

各字段含义:

  • us (user): 用户空间进程消耗的CPU时间

  • sy (system): 内核空间消耗的CPU时间

  • ni (nice): 改变过优先级的进程消耗的CPU时间

  • id (idle): 空闲CPU时间

  • wa (iowait): 等待I/O完成的CPU时间

  • hi (hardware interrupt): 硬件中断消耗的CPU时间

  • si (software interrupt): 软件中断消耗的CPU时间

  • st (steal): 虚拟机被hypervisor偷走的CPU时间

CPU高占用的常见类型

根据不同指标的表现,CPU高占用可以分为几类:

  1. 1. 用户态CPU高(us高):通常是应用程序代码执行导致,如死循环、复杂计算、正则匹配等

  2. 2. 内核态CPU高(sy高):可能是系统调用过多、网络包处理、进程创建销毁频繁等

  3. 3. I/O等待高(wa高):磁盘I/O性能瓶颈,CPU在等待磁盘响应

  4. 4. 软中断高(si高):通常与网络流量大有关,如DDoS攻击、数据传输密集等

关键认知:CPU使用率100%并不代表CPU真正在"工作",wa高的情况下CPU实际是在等待,优化方向完全不同。

为什么说前3分钟最关键?

CPU突然飙高通常伴随着:

  • • 业务响应变慢,用户体验下降

  • • 队列堆积,雪崩效应可能随时发生

  • • 监控数据采集可能受影响,错过关键信息

  • • 黄金时间窗口,趁现场还在赶紧抓取证据

因此,快速定位比完美分析更重要——先止血,再根治。

核心内容:3招定位法详解

第一招:快速锁定问题进程(30秒)

使用top命令快速定位

# 启动top并按CPU使用率排序
top -c

# 关键快捷键:
# P: 按CPU使用率排序
# M: 按内存使用率排序
# c: 显示完整命令行
# 1: 显示每个CPU核心的使用情况

重点关注的信息:

  1. 1. 进程PID和命令:记录占用CPU最高的进程ID和完整命令

  2. 2. CPU%:单个进程占用的CPU百分比(注意多核服务器可能超过100%)

  3. 3. TIME+:进程累计使用的CPU时间

  4. 4. S列(状态):R(运行中)或D(不可中断睡眠,通常在等I/O)

使用htop(如果已安装)

htop
# 更直观的界面,彩色显示,支持鼠标操作
# F5: 树状显示进程关系
# F6: 选择排序字段
# F9: 杀死进程

使用ps命令快速抓取

# 获取CPU占用TOP10的进程
ps aux | head -1; ps aux | sort -rn -k3 | head -10

# 输出示例:
# USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
# www-data  1234 95.3  5.2 2481236 524288 ?     Sl   14:23  12:34 /usr/bin/python3 /app/worker.py

# 查看某个用户的所有进程
ps -u www-data -o pid,ppid,%cpu,%mem,cmd --sort=-%cpu | head -20

实战技巧:一键诊断脚本

#!/bin/bash
# cpu_quick_check.sh - 快速CPU诊断脚本

echo"========== CPU整体状态 =========="
uptime
echo""

echo"========== CPU详细使用率 =========="
top -bn1 | head -20
echo""

echo"========== CPU占用TOP10进程 =========="
ps aux --sort=-%cpu | head -11
echo""

echo"========== 当前CPU核心数 =========="
grep -c processor /proc/cpuinfo
echo""

echo"========== 负载均衡情况 =========="
mpstat -P ALL 1 1

案例分享:某次故障中,top显示一个Java进程CPU占用350%(4核服务器),但实际上是该进程启动了4个线程,每个线程都在满负荷运行。这种情况需要进一步查看线程级别的CPU占用。

第二招:精准定位到具体线程和代码(90秒)

找到问题进程只是第一步,还需要定位到具体是哪个线程、哪行代码导致的问题。

查看进程内的线程CPU占用

# 方法1: 使用top查看线程
top -H -p <PID>
# -H: 显示线程模式
# -p: 指定进程ID

# 方法2: 使用ps查看线程
ps -Lp <PID> -o pid,lwp,ppid,pcpu,comm --sort=-pcpu | head -20
# -L: 显示线程
# lwp: 线程ID

关键步骤:记录CPU占用最高的线程ID(LWP),并转换为16进制(因为堆栈信息中线程ID是16进制)

# 假设线程ID为12345
printf"0x%x\n" 12345
# 输出: 0x3039

Java应用定位到具体代码

# 1. 找到高CPU线程ID
top -H -p <Java进程PID>
# 假设找到线程ID 12345

# 2. 转换为16进制
printf"0x%x\n" 12345  # 得到 0x3039

# 3. 导出Java线程堆栈
jstack <Java进程PID> > /tmp/jstack.log

# 4. 在堆栈文件中搜索该线程
grep -A 20 "0x3039" /tmp/jstack.log

# 一键脚本
show_java_high_cpu_thread() {
local pid=$1
local thread_id=$(top -bn1 -H -p $pid | grep -v PID | sort -rn -k9 | head -1 | awk '{print $1}')
local hex_tid=$(printf"0x%x"$thread_id)
echo"High CPU Thread ID: $thread_id ($hex_tid)"
    jstack $pid | grep -A 30 "$hex_tid"
}

# 使用: show_java_high_cpu_thread <PID>

典型问题代码特征:

  • • 死循环:堆栈一直在同一个方法内

  • • 正则表达式性能问题:堆栈中有java.util.regex.Pattern.matcher

  • • 序列化/反序列化:大量ObjectOutputStreamJSON相关调用

  • • 复杂SQL或ORM:堆栈中有数据库驱动相关代码

Python/Node.js应用定位

Python应用:

# 使用py-spy(需提前安装: pip install py-spy)
py-spy top --pid <PID>

# 查看火焰图
py-spy record -o profile.svg --pid <PID> --duration 30

# 使用gdb(如果py-spy不可用)
gdb -p <PID>
(gdb) py-bt
(gdb) thread apply all py-list

Node.js应用:

# 1. 发送SIGUSR1信号生成CPU profile
kill -USR1 <PID>

# 2. 使用内置profiler
node --prof app.js
# 停止后会生成isolate-xxx-v8.log文件
node --prof-process isolate-xxx-v8.log > processed.txt

# 3. 使用clinic.js(更友好的工具)
npm install -g clinic
clinic doctor -- node app.js

Go应用定位

# 如果应用开启了pprof(通常在代码中引入了net/http/pprof)
# 查看CPU profile
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

# 进入交互式界面后:
(pprof) top 10  # 查看CPU占用前10的函数
(pprof) list <function_name>  # 查看具体函数代码
(pprof) web  # 生成可视化图表(需安装graphviz)

C/C++应用定位

# 使用perf工具(最强大的Linux性能分析工具)
# 1. 记录30秒的CPU采样
perf record -p <PID> -g -- sleep 30

# 2. 查看报告
perf report

# 3. 生成火焰图(需要FlameGraph工具)
perf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg

实战案例:某次故障中,一个Python数据处理服务CPU突然飙升至400%。使用py-spy发现是pandas的merge操作在处理一个300万行的DataFrame。最终通过改为分批处理(chunking)解决,CPU降至30%左右。

第三招:分析系统级资源瓶颈(60秒)

有些情况下,CPU高占用并非应用代码问题,而是系统级资源瓶颈导致。

检查I/O等待

# 查看I/O等待时间
iostat -x 1 5

# 关键指标:
# %util: 接近100%说明磁盘已饱和
# await: 平均等待时间,SSD应<5ms,机械盘应<20ms
# r/s, w/s: 每秒读写次数

# 查看哪些进程在等待I/O
pidstat -d 1 5

# 查看具体文件操作
iotop -o  # 只显示有I/O操作的进程

判断标准:

  • • 如果%wa (iowait)高(>20%),且%util接近100%,说明是磁盘I/O瓶颈

  • • 优化方向:检查是否有大量日志写入、数据库查询未命中索引、大文件操作等

检查网络软中断

# 查看软中断统计
cat /proc/softirqs

# 持续观察软中断变化
watch -d "cat /proc/softirqs | head -20"

# 查看网络流量
iftop -i eth0
# 或者
nload eth0

# 查看网络连接数
netstat -antp | wc -l
ss -s  # 显示socket统计

判断标准:

  • • 如果%si (software interrupt)高(>10%),通常是网络流量大

  • • 常见原因:DDoS攻击、大量短连接、网络包处理不合理

检查进程创建销毁频率

# 查看进程创建速率
pidstat 1 5

# 查看系统调用统计
strace -c -p <PID>

# 查看fork/exec调用频率
perf stat -e 'syscalls:sys_enter_*' -a sleep 10 | grep -E "(fork|exec|clone)"

判断标准:

  • • 如果%sy (system CPU)高(>30%),可能是系统调用过多

  • • 常见原因:CGI程序每次都fork新进程、shell脚本大量调用外部命令

检查上下文切换

# 查看上下文切换频率
vmstat 1 5
# 关注 cs (context switch) 列

# 查看每个进程的上下文切换
pidstat -w 1 5
# cswch/s: 自愿上下文切换(主动让出CPU)
# nvcswch/s: 非自愿上下文切换(被强制切换)

判断标准:

  • • 上下文切换>100万次/秒,可能存在性能问题

  • • 非自愿切换过多,说明进程/线程数过多,CPU调度压力大

检查内存压力

# 查看内存使用情况
free -h

# 查看内存详细信息
cat /proc/meminfo | grep -E "(MemTotal|MemFree|MemAvailable|SwapTotal|SwapFree)"

# 查看是否有OOM killer
dmesg | grep -i "out of memory"
grep -i "out of memory" /var/log/messages

# 查看内存分配
slabtop

判断标准:

  • • 如果内存不足导致频繁swap,CPU会消耗在内存页换入换出上

  • • MemAvailable < 10%总内存,且swap使用率高,需要关注

综合诊断命令

# 使用dstat综合查看(需安装)
dstat -tcmndylsp --top-cpu --top-mem 5

# 使用sar(系统活动报告)
sar -u 1 10  # CPU使用率
sar -q 1 10  # 负载和运行队列
sar -w 1 10  # 上下文切换

# 一键生成诊断报告
(
echo"=== System Load ==="
uptime
echo""
echo"=== CPU Stats ==="
mpstat -P ALL 1 1
echo""
echo"=== Memory Stats ==="
free -h
echo""
echo"=== I/O Stats ==="
iostat -x 1 1
echo""
echo"=== Network Stats ==="
ss -s
echo""
echo"=== Top Processes ==="
ps aux --sort=-%cpu | head -20
) | tee cpu_diagnosis_$(date +%Y%m%d_%H%M%S).txt

实践案例:真实故障的完整诊断流程

案例背景

某电商API网关服务,在正常业务流量下突然CPU飙升至98%,持续10分钟未恢复。API响应时间从平均50ms飙升到5000ms,触发大量超时告警。

完整诊断过程

14:32:15 - 收到告警,立即SSH登录服务器

第一招:快速锁定进程(用时25秒)

$ top -c
# 发现 nginx worker进程 CPU占用 780%(8核服务器)
# PID 18234, USER: www-data

第二招:定位具体问题(用时90秒)

$ top -H -p 18234
# 发现并非某个线程特别高,而是8个worker都均衡在100%左右
# 说明不是某个请求的问题,而是整体流量或配置问题

$ strace -c -p 18234
# 发现大量的 epoll_wait, recvfrom, writev系统调用
# 没有异常的系统调用模式

$ netstat -antp | grep :80 | wc -l
# 连接数3200,属于正常范围(最大10000)

$ iftop -i eth0
# 发现网络流量正常,出入带宽都未打满

第三招:系统级分析(用时60秒)

$ iostat -x 1 3
# I/O正常,%wa只有2%

$ cat /proc/softirqs | head -20
# NET_RX (网络接收软中断) 数值增长快,但不算异常

$ tail -100 /var/log/nginx/access.log
# 发现大量对同一个API的请求: GET /api/product/detail?id=12345
# 平均每秒300+请求

$ curl -w "%{time_total}\n" -o /dev/null -s http://localhost/api/product/detail?id=12345
# 响应时间: 8.234秒! (正常应该0.05秒)

问题定位(用时3分10秒)

定位到是/api/product/detail接口响应极慢,导致nginx worker被占满。进一步排查后端PHP-FPM:

$ ps aux | grep php-fpm
# 发现100个php-fpm进程全部处于"R"状态(运行中),CPU都很高

$ strace -p <php-fpm-PID>
# 发现在反复调用 poll系统调用,疑似在等待某个网络请求

$ grep "product/detail" /var/log/php/error.log
# 发现大量: "Warning: mysqli_connect(): (HY000/2002): Connection timed out"

根因确认:

  1. 1. 数据库连接超时,每个请求都在等待MySQL连接建立(默认超时30秒)

  2. 2. 同时有100个请求在等待,用户端不断重试,形成恶性循环

  3. 3. 进一步检查发现MySQL主库宕机,VIP未切换到从库

处理措施:

  1. 1. 紧急手动切换MySQL主从(3分钟)

  2. 2. 重启PHP-FPM清空阻塞的请求(1分钟)

  3. 3. 逐步放开流量,监控恢复情况(5分钟)

总耗时: 从告警到恢复,共12分钟

复盘总结

做得好的地方:

  • • ✅ 3招定位法帮助快速找到表层现象(nginx CPU高)

  • • ✅ 未局限于nginx本身,顺藤摸瓜找到后端PHP和数据库问题

  • • ✅ 保留了关键日志和strace输出,便于复盘

可改进之处:

  • • ⚠️ 数据库主从切换不应手动,应配置自动切换(MHA/Orchestrator)

  • • ⚠️ PHP-FPM应配置更合理的超时时间,避免长时间阻塞

  • • ⚠️ 应增加数据库连接失败的监控告警,更早发现问题

最佳实践与预防措施

日常监控配置

必备的CPU监控指标:

# Prometheus监控规则示例
groups:
-name:cpu_alerts
rules:
# 总CPU使用率告警
-alert:HighCPUUsage
expr:100-(avgby(instance)(irate(node_cpu_seconds_total{mode="idle"}[5m]))*100)>80
for:3m
labels:
severity:warning
annotations:
summary:"CPU使用率超过80%持续3分钟"

# I/O等待告警
-alert:HighIOWait
expr:avgby(instance)(irate(node_cpu_seconds_total{mode="iowait"}[5m]))*100>20
for:2m
labels:
severity:warning
annotations:
summary:"I/O等待超过20%"

# 系统态CPU告警
-alert:HighSystemCPU
expr:avgby(instance)(irate(node_cpu_seconds_total{mode="system"}[5m]))*100>30
for:3m
labels:
severity:warning

# 负载告警(load average / CPU核心数 > 2)
-alert:HighLoadAverage
expr:node_load5/count(node_cpu_seconds_total{mode="system"})by(instance)>2
for:5m
labels:
severity:warning

应用层优化建议

1. 合理设置超时时间

# Python示例:避免无限期等待
import requests

response = requests.get(
'http://api.example.com/data',
    timeout=(3, 10)  # (连接超时, 读取超时)
)

# MySQL连接池配置
mysql_pool = {
'connect_timeout': 5,
'read_timeout': 30,
'write_timeout': 30
}

2. 避免CPU密集型操作阻塞主线程

// Node.js示例:使用worker threads处理CPU密集任务
const { Worker } = require('worker_threads');

functionheavyComputation(data) {
returnnewPromise((resolve, reject) => {
const worker = newWorker('./heavy_task.js', {
workerData: data
        });
        worker.on('message', resolve);
        worker.on('error', reject);
    });
}

3. 正则表达式优化

// Java示例:避免灾难性回溯
// 危险: (a+)+b 对于 "aaaa...aaa"(无b) 会导致指数级回溯
// 安全: a+b
Patternpattern= Pattern.compile("a+b");

// 预编译正则表达式,避免重复编译
privatestaticfinalPatternEMAIL_PATTERN= Pattern.compile("^[\\w.-]+@[\\w.-]+\\.[a-z]{2,}$");

4. 限流和熔断

// Go示例:使用令牌桶限流
import"golang.org/x/time/rate"

limiter := rate.NewLimiter(rate.Limit(100), 200) // 每秒100个,桶容量200

funchandleRequest(w http.ResponseWriter, r *http.Request) {
if !limiter.Allow() {
        http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
return
    }
// 正常处理请求
}

系统层优化建议

1. 调整CPU调度策略

# 为关键进程设置更高优先级
renice -n -10 -p <PID>

# 绑定进程到特定CPU核心(避免cache miss)
taskset -cp 0-3 <PID>  # 绑定到0-3号核心

2. 优化网络参数

# /etc/sysctl.conf
# 增加软中断处理能力
net.core.netdev_max_backlog = 5000
net.core.somaxconn = 1024

# 启用RPS(Receive Packet Steering),分散网络处理到多核
# 在 /etc/rc.local 中添加:
for irq in $(grep eth0 /proc/interrupts | cut -d: -f1); do
echo f > /proc/irq/$irq/smp_affinity
done

3. I/O调度器优化

# 查看当前I/O调度器
cat /sys/block/sda/queue/scheduler

# 对于SSD,使用noop或deadline
echo noop > /sys/block/sda/queue/scheduler

# 对于HDD,使用cfq
echo cfq > /sys/block/sda/queue/scheduler

应急工具箱

建立一个快速诊断工具集:

#!/bin/bash
# /opt/scripts/cpu_emergency_toolkit.sh

functioncheck_top_process() {
echo"=== TOP 10 CPU进程 ==="
    ps aux --sort=-%cpu | head -11
}

functioncheck_io_wait() {
echo"=== I/O等待检查 ==="
    iostat -x 1 3
}

functioncheck_network() {
echo"=== 网络连接数 ==="
    ss -s
echo"=== 软中断 ==="
    grep -E "(NET_RX|NET_TX)" /proc/softirqs | head -5
}

functiondump_java_thread() {
local pid=$1
if [ -z "$pid" ]; then
echo"Usage: dump_java_thread <PID>"
return
fi
local output="/tmp/jstack_$(date +%Y%m%d_%H%M%S).log"
    jstack $pid > $output
echo"Java线程堆栈已保存到: $output"
# 自动分析高CPU线程
local thread_id=$(top -bn1 -H -p $pid | grep -v PID | sort -rn -k9 | head -1 | awk '{print $1}')
local hex_tid=$(printf"0x%x"$thread_id)
echo"=== 高CPU线程 $thread_id ($hex_tid) ==="
    grep -A 30 "$hex_tid"$output
}

functionfull_diagnosis() {
local report="/tmp/cpu_diagnosis_$(date +%Y%m%d_%H%M%S).txt"
    {
echo"========== CPU诊断报告 =========="
echo"生成时间: $(date)"
echo""
        check_top_process
echo""
        check_io_wait
echo""
        check_network
echo""
echo"=== 系统负载 ==="
uptime
echo""
echo"=== CPU详情 ==="
        mpstat -P ALL 1 1
echo""
echo"=== 内存 ==="
        free -h
    } | tee$report
echo""
echo"完整诊断报告已保存到: $report"
}

# 提供命令行接口
case"$1"in
    top) check_top_process ;;
    io) check_io_wait ;;
    net) check_network ;;
    java) dump_java_thread $2 ;;
    full) full_diagnosis ;;
    *)
echo"Usage: $0 {top|io|net|java <PID>|full}"
exit 1
        ;;
esac

使用方法:

# 快速检查
/opt/scripts/cpu_emergency_toolkit.sh top

# 完整诊断
/opt/scripts/cpu_emergency_toolkit.sh full

# Java应用诊断
/opt/scripts/cpu_emergency_toolkit.sh java 12345

总结

服务器CPU飙到100%是运维工作中最常见的性能问题之一。通过本文介绍的"3招3分钟定位法"——快速锁定问题进程、精准定位到具体线程和代码、分析系统级资源瓶颈——可以在大多数情况下快速找到根本原因。

核心要点

  1. 1. 分层定位:从进程→线程→代码,逐层深入

  2. 2. 全面分析:不仅看CPU,还要看I/O、网络、内存、上下文切换等

  3. 3. 保留证据:诊断过程中的输出要保存,便于复盘和深度分析

  4. 4. 工具先行:准备好一键诊断脚本,紧急时刻不慌乱

Last updated