Linux服务器突发CPU飙高?一键定位脚本与排查思路

Linux服务器突发CPU飙高?一键定位脚本与排查思路

前言:CPU飙高的紧急处理哲学

线上服务器CPU使用率突然飙升到90%以上,告警短信蜂拥而至,业务响应缓慢。此时,你的操作步骤至关重要:

  1. 保持冷静,快速定位:切忌盲目重启服务,可能丢失宝贵的问题现场。
  2. 保留现场,最小影响:在尽可能短的时间内收集足够的诊断信息。
  3. 精准打击,快速恢复:定位到根因后,采取精准措施(如重启单实例、扩容)而非整机重启。

本文将提供一个全自动的一键排查脚本,并详细解释其背后的排查思路,让你在下次遇到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

使用方法

  1. chmod +x cpu_perf_analysis.sh
  2. ./cpu_perf_analysis.sh (默认采样30秒)
  3. ./cpu_perf_analysis.sh 60 199 (采样60秒,频率199Hz,更高频率能捕获更多细节但开销稍大)

二、脚本输出解读与排查思路

脚本运行后,会自动生成一个目录(如 /tmp/cpu_perf_20240612_134522/),其中包含所有诊断文件。你的排查思路应遵循以下流程:

第一步:快速定位异常进程

查看文件:diagnosis_summary.txttop_processes.txt

  • 目标是立即找到 CPU占用率最高 的进程(PID)和其对应的命令
  • 常见元凶:
    • java:Java应用逻辑问题或GC频繁。
    • php-fpmpythonnode:应用代码中出现死循环或异常逻辑。
    • mysqldredis-server:数据库慢查询或锁竞争。
    • kworkerksoftirqd:内核线程,可能由硬件中断或网络问题导致。
    • ddcp:异常的数据拷贝任务。

第二步:深入分析异常进程

如果是Java应用(最常见):

  1. 查看 java_status.txt:确认是哪个Java进程。
  2. 查看 jstack_<PID>.log:这是关键!获取应用的线程堆栈。
  3. 查看 java_hot_threads.txt:脚本已自动筛选出状态为 RUNNABLE 的线程,这些很可能就是“热点”线程。
  4. 分析热点线程:在 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.txtvmstat.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飙高问题排查的数据收集过程,但最终的分析判断仍需你的智慧。核心思路永远是:

  1. 找到谁在忙(进程、线程)。
  2. 弄清楚它在忙什么(堆栈、火焰图)。
  3. 采取正确的措施(修复、优化、扩容)。

建议在日常就将此脚本部署到关键服务器上,或在镜像中打包,真正做到“一键定位”,为稳定护航。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

evil robot

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值