Linux服务器突发CPU飙高?一键定位脚本与排查思路
前言:CPU飙高的紧急处理哲学
线上服务器CPU使用率突然飙升到90%以上,告警短信蜂拥而至,业务响应缓慢。此时,你的操作步骤至关重要:
- 保持冷静,快速定位:切忌盲目重启服务,可能丢失宝贵的问题现场。
- 保留现场,最小影响:在尽可能短的时间内收集足够的诊断信息。
- 精准打击,快速恢复:定位到根因后,采取精准措施(如重启单实例、扩容)而非整机重启。
本文将提供一个全自动的一键排查脚本,并详细解释其背后的排查思路,让你在下次遇到CPU飙高时,能够从容应对。
一、一键定位自动化脚本
将以下脚本保存为 cpu_perf_analysis.sh 并赋予执行权限,在问题发生时直接运行即可。
#!/bin/bash
# =============================================================================
# 名称: CPU性能瓶颈一键排查脚本
# 描述: 用于Linux服务器CPU使用率突然飙高时的快速诊断
# 版本: v1.2
# 作者: Your Name
# 日期: 2024-06-12
# 使用: ./cpu_perf_analysis.sh [duration=30] [frequency=99]
# =============================================================================
set -eu
# 默认采样时长(秒)和频率(Hz)
DURATION=${1:-30}
FREQUENCY=${2:-99}
# 输出目录
OUTPUT_DIR="/tmp/cpu_perf_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$OUTPUT_DIR"
echo "诊断数据将输出至: $OUTPUT_DIR"
# 函数: 收集系统概览信息
collect_overview() {
echo "收集系统概览信息..."
top -bn1 | head -20 > "$OUTPUT_DIR/top_overview.txt"
vmstat 1 5 > "$OUTPUT_DIR/vmstat.txt"
mpstat -P ALL 1 5 > "$OUTPUT_DIR/mpstat.txt"
sar -n DEV 1 5 > "$OUTPUT_DIR/sar_network.txt"
iostat -xz 1 5 > "$OUTPUT_DIR/iostat.txt"
free -h > "$OUTPUT_DIR/memory.txt"
}
# 函数: 快速抓取消耗CPU最高的进程
snapshot_top_processes() {
echo "抓取CPU消耗最高的进程..."
# 抓取3次,避免瞬时进程干扰
for i in {1..3}; do
echo "[Snapshot $i]" >> "$OUTPUT_DIR/top_processes.txt"
ps aux --sort=-%cpu | head -20 >> "$OUTPUT_DIR/top_processes.txt"
echo -e "\n" >> "$OUTPUT_DIR/top_processes.txt"
sleep 2
done
}
# 函数: 使用Perf录制性能剖析数据 (CPU FlameGraph)
record_perf_data() {
# 检查perf是否安装
if ! command -v perf &> /dev/null; then
echo "警告: perf 未安装,无法生成火焰图。可尝试安装: apt-get install linux-tools-common linux-tools-$(uname -r)"
return 1
fi
echo "开始Perf采样,时长 ${DURATION} 秒..."
# 抓取所有CPU的调用栈
perf record -F "$FREQUENCY" -ag -- sleep "$DURATION" > "$OUTPUT_DIR/perf_record.log" 2>&1
perf script > "$OUTPUT_DIR/perf_script.out"
# 尝试生成火焰图 (需提前安装FlameGraph工具集)
if command -v stackcollapse-perf.pl &> /dev/null && command -v flamegraph.pl &> /dev/null; then
echo "生成CPU火焰图..."
stackcollapse-perf.pl "$OUTPUT_DIR/perf_script.out" > "$OUTPUT_DIR/out.folded"
flamegraph.pl "$OUTPUT_DIR/out.folded" > "$OUTPUT_DIR/cpu_flamegraph.svg"
echo "火焰图已生成: $OUTPUT_DIR/cpu_flamegraph.svg (可使用浏览器打开)"
else
echo "未找到FlameGraph脚本,请从 https://github.com/brendangregg/FlameGraph 下载并配置PATH"
fi
}
# 函数: 检查Java应用情况
check_java_processes() {
echo "检查Java进程..."
# 查找Java进程
JAVA_PIDS=$(ps aux | grep -E '[j]ava|[t]omcat|[s]pring' | awk '{print $2}' | tr '\n' ' ')
if [ -z "$JAVA_PIDS" ]; then
echo "未找到Java进程。" >> "$OUTPUT_DIR/java_status.txt"
return
fi
for PID in $JAVA_PIDS; do
echo "=== 诊断Java进程 PID: $PID ===" >> "$OUTPUT_DIR/java_status.txt"
# 获取线程CPU占用情况
top -H -bn1 -p "$PID" | head -30 >> "$OUTPUT_DIR/java_status.txt"
echo -e "\n" >> "$OUTPUT_DIR/java_status.txt"
# 尝试获取jstack信息 (如果权限和用户匹配)
if sudo -u "$(ps -o user= -p $PID)" jstack "$PID" > "$OUTPUT_DIR/jstack_${PID}.log" 2>&1; then
echo "已捕获jstack信息: $OUTPUT_DIR/jstack_${PID}.log"
# 从jstack中找出Runnable状态的线程
grep -A 1 "java.lang.Thread.State: RUNNABLE" "$OUTPUT_DIR/jstack_${PID}.log" | head -20 >> "$OUTPUT_DIR/java_hot_threads.txt"
else
echo "无法为PID $PID 执行jstack命令。" >> "$OUTPUT_DIR/java_status.txt"
fi
done
}
# 函数: 生成诊断报告摘要
generate_summary() {
echo "生成诊断报告摘要..."
{
echo "CPU性能诊断报告"
echo "=================="
echo "诊断时间: $(date)"
echo "主机名: $(hostname)"
echo "运行时长: $(uptime)"
echo "内核版本: $(uname -r)"
echo "=================="
echo "**1. 系统负载概况:**"
cat "$OUTPUT_DIR/top_overview.txt" | head -5
echo -e "\n**2. CPU最高进程快照 (前10):**"
cat "$OUTPUT_DIR/top_processes.txt" | head -12 | tail -10
echo -e "\n**3. 可能的热点Java线程 (如有):**"
if [ -f "$OUTPUT_DIR/java_hot_threads.txt" ]; then
cat "$OUTPUT_DIR/java_hot_threads.txt" | head -10
else
echo "无Java进程或无法捕获线程信息。"
fi
echo -e "\n**4. 下一步建议:**"
echo "- 查看详细文件: top_processes.txt, java_status.txt"
echo "- 分析火焰图: cpu_flamegraph.svg (如果已生成)"
echo "- 检查IO和网络状态: iostat.txt, sar_network.txt"
} > "$OUTPUT_DIR/diagnosis_summary.txt"
echo "========================================"
echo "诊断完成! 请查阅以下关键文件:"
echo "========================================"
echo "1. 报告摘要: $OUTPUT_DIR/diagnosis_summary.txt"
echo "2. 进程快照: $OUTPUT_DIR/top_processes.txt"
echo "3. Java状态: $OUTPUT_DIR/java_status.txt"
if [ -f "$OUTPUT_DIR/cpu_flamegraph.svg" ]; then
echo "4. **火焰图: $OUTPUT_DIR/cpu_flamegraph.svg **"
fi
echo "========================================"
}
# 主执行逻辑
main() {
echo "🚀 开始CPU性能瓶颈诊断 (采样时长: ${DURATION}s) ..."
collect_overview
snapshot_top_processes
check_java_processes & # 后台执行
record_perf_data # 这是耗时最长的操作
wait # 等待后台任务完成
generate_summary
echo "✅ 所有诊断数据已收集完毕。请根据摘要分析根因。"
}
# 执行主函数
main
使用方法:
chmod +x cpu_perf_analysis.sh./cpu_perf_analysis.sh(默认采样30秒)./cpu_perf_analysis.sh 60 199(采样60秒,频率199Hz,更高频率能捕获更多细节但开销稍大)
二、脚本输出解读与排查思路
脚本运行后,会自动生成一个目录(如 /tmp/cpu_perf_20240612_134522/),其中包含所有诊断文件。你的排查思路应遵循以下流程:
第一步:快速定位异常进程
查看文件:diagnosis_summary.txt 和 top_processes.txt
- 目标是立即找到 CPU占用率最高 的进程(PID)和其对应的命令。
- 常见元凶:
java:Java应用逻辑问题或GC频繁。php-fpm、python、node:应用代码中出现死循环或异常逻辑。mysqld、redis-server:数据库慢查询或锁竞争。kworker、ksoftirqd:内核线程,可能由硬件中断或网络问题导致。dd、cp:异常的数据拷贝任务。
第二步:深入分析异常进程
如果是Java应用(最常见):
- 查看
java_status.txt:确认是哪个Java进程。 - 查看
jstack_<PID>.log:这是关键!获取应用的线程堆栈。 - 查看
java_hot_threads.txt:脚本已自动筛选出状态为RUNNABLE的线程,这些很可能就是“热点”线程。 - 分析热点线程:在
jstack_<PID>.log中搜索热点线程的nid(十六进制线程ID),找到对应的代码堆栈。- 示例:
java_hot_threads.txt中有一个线程的nid=0x5a3d。 - 在
jstack_<PID>.log中搜索5a3d(注意是十进制23101或十六进制0x5a3d),你会看到类似:"http-nio-8080-exec-1" #23 daemon prio=5 os_prio=0 tid=0x00007f487c00e800 nid=0x5a3d runnable [0x00007f486b7fe000] java.lang.Thread.State: RUNNABLE at com.example.app.ExpensiveService.calculate(ExpensiveService.java:42) <--- !!! 问题代码行 ... - 至此,你已精准定位到导致CPU飙高的具体类和方法。
- 示例:
如果是其他进程或内核线程:
- 查看
cpu_flamegraph.svg:用浏览器打开火焰图。最宽的“火苗”就是最耗CPU的函数。它能直观展示代码路径上的性能瓶颈,无论是用户态还是内核态代码。 - 查看
mpstat.txt和vmstat.txt:- 如果
%iowait很高,瓶颈可能在磁盘IO(结合iostat.txt分析)。 - 如果
in(中断次数)很高,可能是有网络或硬件中断问题。
- 如果
第三步:采取行动
- 代码BUG:定位到问题代码后,联系开发团队修复。
- GC频繁:如果
jstack显示全是GC线程,需要调整JVM内存参数。 - 慢查询:如果是数据库进程,立刻排查慢查询日志。
- 瞬时压力:如果是正常流量高峰,考虑扩容或限流。
- 无法解决:保留现场后,再考虑重启服务。
三、手动命令备用(当无法运行脚本时)
如果无法上传脚本,记住以下关键命令组合拳:
# 1. 快速定位高CPU进程
top -c -n 1 | head -20
# 2. 查看具体进程的线程CPU占用
ps -eLo pid,lwp,pcpu,comm | grep <PID> | sort -k3 -nr | head
# 3. 抓取Java线程栈 (对Java应用)
jstack <PID> > jstack.log
# 4. 将线程ID转换为16进制 (用于在jstack中查找)
printf "%x\n" <LWP_ID>
# 5. 系统级监控
vmstat 1 5
iostat -xz 1 5
总结
本脚本自动化了CPU飙高问题排查的数据收集过程,但最终的分析判断仍需你的智慧。核心思路永远是:
- 找到谁在忙(进程、线程)。
- 弄清楚它在忙什么(堆栈、火焰图)。
- 采取正确的措施(修复、优化、扩容)。
建议在日常就将此脚本部署到关键服务器上,或在镜像中打包,真正做到“一键定位”,为稳定护航。
1109

被折叠的 条评论
为什么被折叠?



