云计算百科
云计算领域专业知识百科平台

【Linux从入门到精通】第49篇:服务器故障排查终极指南——思路决定出路

目录

一、排查框架:接到告警后第一件事做什么

1.1 黄金5分钟:建立全局认知

1.2 快速定位方向:看vmstat决定下一步

二、场景一:CPU飙升100%

2.1 现象

2.2 排查流程

2.3 CPU排查总结

三、场景二:内存泄漏排查

3.1 现象

3.2 排查流程

3.3 内存泄漏排查总结

四、场景三:磁盘满了但找不到大文件

4.1 现象

4.2 排查流程

4.3 磁盘排查总结

五、综合排查速查表

六、本篇小结

动手练习

七、下篇预告


一、排查框架:接到告警后第一件事做什么

1.1 黄金5分钟:建立全局认知

半夜被报警叫醒,你最不该做的就是随机敲命令乱试。没有明确方向的排查,只会浪费宝贵的恢复时间。前5分钟,只做一件事:建立全局认知,锁定问题范围。

标准流程只有五条命令:

bash

# 1. 系统整体负载(1分钟/5分钟/15分钟平均负载)
uptime

# 2. 内存概况(available是最关键的指标)
free -h

# 3. 磁盘空间(满了什么都做不了)
df -h

# 4. 系统整体负载分解(r=CPU,b=I/O,wa=等待I/O)
vmstat 2 5

# 5. 最近的系统日志(内核、服务、OOM等异常)
journalctl -p err –since "30 min ago"

做完这五条,回答自己三个问题:

  • 负载高吗?(uptime vs CPU核心数)

  • 负载是哪来的?(vmstat的r列还是b列高?wa有多高?)

  • 最近有什么异常事件?(OOM Killer?服务重启?磁盘错误?)

  • 1.2 快速定位方向:看vmstat决定下一步

    text

    vmstat 2

    ├── r 列持续 > CPU核心数 ──→ CPU瓶颈

    ├── b 列持续 > 0 ──→ I/O瓶颈

    ├── si/so 持续 > 0 ──→ 内存瓶颈(正在换页)

    └── 三者都正常 ──→ 网络或应用层问题

    这4个判断足够覆盖90%的生产故障。 方向错了,后面的所有排查都是浪费时间。接下来,我们按三种最经典的故障场景,逐一展开完整的排查流程。

    二、场景一:CPU飙升100%

    2.1 现象

    监控告警:某台服务器的CPU使用率突然飙升到100%,且vmstat确认r列远大于CPU核心数。

    2.2 排查流程

    第一步:找出吃CPU的进程

    bash

    top -c
    # 按 P 确保按CPU排序
    # 记下排在最前面的PID和进程名

    假设PID为12345,进程名是java(某个Java应用)。

    第二步:细化到线程级别

    对于多线程应用(Java、MySQL),一个进程的CPU高通常是某几个线程导致的:

    bash

    # 查看进程内每个线程的CPU使用率
    top -H -p 12345
    # 记下最高CPU的线程ID(TID),假设为 12389

    第三步:将线程ID转十六进制

    bash

    printf "%x\\n" 12389
    # 输出:3065

    第四步:查看该线程的调用栈

    bash

    # jstack是Java专用工具,输出进程内所有线程的调用栈
    # 用十六进制TID(3065)搜索对应的线程
    jstack 12345 | grep -A 20 3065

    # 如果是其他应用(非Java),用`perf`采样CPU调用栈
    sudo perf top -p 12345

    输出会显示线程当前正在执行的函数名。从调用栈中的函数名可以判断:

    • 频繁GC(垃圾回收)→ 内存不够,检查堆大小或排查内存泄漏

    • 正则表达式匹配 → 可能是正则回溯问题或恶意输入

    • 业务代码的死循环 → 代码逻辑bug

    • 加密/压缩等计算密集函数 → 正常的计算任务,考虑优化算法或限流

    2.3 CPU排查总结

    text

    top → 找高CPU进程
    top -H -p PID → 找高CPU线程(多线程应用)
    jstack/perf top → 看调用栈找根因

    三种常见根因:

    • 死循环:业务代码bug

    • 频繁GC:内存不足或内存泄漏导致

    • 正常计算密集:加密/压缩/转码等,考虑加CPU或限流

    三、场景二:内存泄漏排查

    3.1 现象

    服务器内存使用率持续增长,即使业务低谷期也不下降。重启后恢复正常,但运行几天后又涨回去。这是典型的内存泄漏特征。

    3.2 排查流程

    第一步:确认内存分布

    bash

    free -h
    # available持续减少,buff/cache没有增加 → 匿名页(程序堆/栈)在增长

    buff/cache是文件缓存,涨了不意味着泄漏。如果used不断增加而buff/cache基本不变,说明应用在持续申请匿名内存。

    第二步:找出内存大户

    bash

    ps aux –sort=-%mem | head -10
    # 或者用更精确的PSS统计
    sudo smem -p -s pss | head -10

    假设PID为12345的进程内存占用不停增长。smem的PSS(Proportional Set Size,比例共享大小)比RSS更准确,因为它把共享库的内存按比例分摊给了各个使用它的进程。

    第三步:查看该进程的内存增长趋势

    bash

    # 每10秒打印一次内存使用,持续观察
    while true; do
    echo "$(date +%H:%M:%S) $(smem -p -c "pid pss" -P 12345 | tail -1)"
    sleep 10
    done

    如果PSS持续增长 → 确认泄漏存在于该进程中。

    第四步:查看进程内存分配细节

    bash

    pmap -x 12345 | tail -1
    # 或者
    cat /proc/12345/status | grep -E "VmRSS|VmSize"

    pmap显示该进程的虚拟内存映射——每一段内存的起始地址、权限、大小和来源(映射的是哪个文件还是匿名分配)。如果某段匿名内存(anon)异常大(比如占总内存的80%以上),可以基本确认是程序内部在持续申请内存而不释放。

    第五步:使用valgrind深入分析(开发环境)

    bash

    # Valgrind的memcheck工具会模拟执行程序,追踪每一块内存的分配和释放
    # 注意:这会使程序运行速度减慢10-20倍,只能在开发环境使用!
    valgrind –leak-check=full –log-file=valgrind.log ./my_app
    cat valgrind.log | grep "definitely lost"

    输出会精确指出代码中哪个文件、哪一行分配的哪块内存没有被释放。这个信息交给开发人员,可以精确定位泄漏点。

    3.3 内存泄漏排查总结

    text

    free -h → 确认内存增长类型
    ps/smem → 找到内存增长进程
    pmap/proc/PID/status → 确认进程中哪部分内存异常(匿名页还是共享内存)
    valgrind(开发环境)→ 定位到具体代码行

    常见根因:

    • 应用代码bug:忘记释放内存、循环中反复分配

    • 缓存未设上限:进程内缓存(如HashMap)无限增长

    • 线程泄漏:线程创建后未正确回收,每个线程默认占用约8MB栈空间

    四、场景三:磁盘满了但找不到大文件

    4.1 现象

    df -h报告磁盘使用率90%以上,但用du -sh /*逐层排查,所有目录加起来的大小远小于df显示的已用空间。

    为什么df和du结果对不上?

    • df是从文件系统元数据中直接读取的块使用量,反映的是磁盘块分配情况

    • du是遍历目录树,把每个可见文件的大小累加起来

    • 如果有文件被删除了但进程还持有它的文件描述符,du统计不到,但df知道这些磁盘块没有被释放

    4.2 排查流程

    第一步:确认df和du的差异

    bash

    df -h /
    du -sh / 2>/dev/null
    # 如果df显示40G已用,du显示30G,但加起来确实找不到后10G在哪

    这种悬殊差异(差了几个GB而目录遍历完全找不到对应文件),说明有被删除但仍在被进程持有的文件。

    第二步:找出“已删除但仍被占用的文件”

    bash

    sudo lsof | grep deleted | awk '{print $1, $2, $NF}' | sort -u
    # 或者更精确地显示文件大小
    sudo lsof +L1

    +L1是lsof的专用参数,表示“列出所有链接计数小于1的文件”,即被删除但进程仍打开着的文件。输出示例:

    text

    COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
    java 12345 app 12w REG 253,1 8589934592 0 56789 /var/log/app.log (deleted)

    这条输出显示:进程java(PID 12345)的文件描述符12w,指向一个已被删除的文件/var/log/app.log,它占用了8.5GB的磁盘空间。那个“(deleted)”标记就是罪魁祸首——文件已不在目录中,但内核因为进程还打开着它,不会释放它的磁盘块。

    第三步:释放空间

    bash

    # 如果是日志文件,直接清空
    sudo truncate -s 0 /proc/12345/fd/12

    # 或者让进程重新打开日志文件(更优雅,需要进程支持)
    kill -HUP 12345 # 发送SIGHUP,许多守护进程会重新打开日志文件

    # 如果不关心这个文件的内容,直接杀掉进程
    sudo kill 12345

    truncate vs kill:truncate -s 0通过/proc/PID/fd/文件描述符来截断文件内容,进程无感知,不会中断服务。kill会终止进程,如果这个进程是关键服务,可能造成宕机。因此优先用truncate。

    4.3 磁盘排查总结

    text

    df -h → 确认分区已用空间
    du -sh /* → 找到空间占用大的目录
    df和du结果悬殊 → 被删除文件仍被占用(lsof +L1)
    lsof +L1 → 找到进程和文件描述符
    truncate -s 0 /proc/PID/fd/FD → 释放空间

    常见根因:

    • 日志轮替不当:应用在持续写入,logrotate脚本用rm删了文件但没有用kill -HUP通知应用重新打开日志句柄。文件虽删,数据继续往内核的旧inode里写

    • 临时文件未清理:应用创建的临时文件被删除,但句柄未关闭

    • 大文件仍被进程持有:有人rm了一个大文件但不知道有进程还在用它

    五、综合排查速查表

    现象第一步命令第二步命令关键判断
    负载高 uptime + vmstat 2 r高→第三列,b高→第四列 r>CPU核数=CPU瓶颈,b>0=I/O瓶颈
    CPU高 top -c top -H -p PID + jstack/perf top 找吃CPU的线程和调用栈
    内存高 free -h smem -p -s pss available持续下降=内存不足
    内存泄漏 smem观察趋势 pmap -x PID + valgrind 进程PSS只涨不跌=泄漏
    磁盘满 df -h du -sh /* 2>/dev/null 逐层深入 df和du结果悬殊=被删文件仍被占用
    磁盘满(隐形) df -h vs du -sh lsof +L1 查(deleted)文件并清空或kill -HUP
    网络异常 ping -c 4 网关 ss -tanp 确认连通性和端口状态

    六、本篇小结

    排查黄金法则:

  • 先宏观后微观:先看全局(uptime/vmstat/df),锁定方向后再深入细节

  • 先排除最简单的原因:磁盘满了、服务挂了、内存溢出了——这些远比代码bug更常见

  • 一次只验证一个假设:不要同时改三个配置,改完一个就验证,否则不知道哪个生效了

  • 每一步保存证据:script命令记录终端操作,sar历史数据回溯问题发生时的系统状态

  • 三个高频故障的完整链路:

    • CPU飙升:top找进程 → top -H找线程 → jstack/perf top看调用栈

    • 内存泄漏:smem找进程 → pmap确认泄漏区域 → valgrind(开发环境)定位代码行

    • 磁盘隐形占用:df vs du发现差异 → lsof +L1找(deleted)文件 → truncate或kill -HUP释放

    动手练习

    bash

    # 1. 完整跑一次系统体检流程
    uptime && free -h && df -h && vmstat 2 5

    # 2. 模拟“删除了文件但空间不释放”的场景
    dd if=/dev/zero of=/tmp/bigdel bs=1M count=100
    exec 3<>/tmp/bigdel # 打开一个文件描述符持有文件
    rm /tmp/bigdel # 删除文件
    df -h /tmp # 注意空间没释放
    lsof +L1 | grep bigdel # 找到这个“幽灵”文件
    exec 3>&- # 关闭文件描述符,空间释放
    df -h /tmp # 验证空间已恢复

    # 3. 观察进程的内存变化
    smem -p -s pss | head -10

    七、下篇预告

    这是专栏的倒数第二篇文章。我们走过了50篇的技术学习——从第1篇的“为什么要学Linux”到第48篇的集群高可用,从最基础的cd、ls命令到内核函数级别的动态追踪。

    最后一篇《专栏总结与Linux学习之路的未来展望》将回顾整条学习路径,你可以对照每一篇的标题检查自己是否掌握核心操作和排查思路。我们将梳理Linux在当前技术生态中的角色演变——从传统的服务器操作系统,到云计算的基础镜像、Kubernetes的容器运行时、AI训练平台的底层支撑;并为你规划后续的深入方向:容器编排、可观测性体系、内核开发等领域的学习路线图。这是整个专栏的终点,也是你Linux技能持续生长的起点。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【Linux从入门到精通】第49篇:服务器故障排查终极指南——思路决定出路
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!