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

目录

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

[1.1 黄金5分钟:建立全局认知](#1.1 黄金5分钟:建立全局认知)

[1.2 快速定位方向:看vmstat决定下一步](#1.2 快速定位方向:看vmstat决定下一步)

二、场景一:CPU飙升100%

[2.1 现象](#2.1 现象)

[2.2 排查流程](#2.2 排查流程)

[2.3 CPU排查总结](#2.3 CPU排查总结)

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

[3.1 现象](#3.1 现象)

[3.2 排查流程](#3.2 排查流程)

[3.3 内存泄漏排查总结](#3.3 内存泄漏排查总结)

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

[4.1 现象](#4.1 现象)

[4.2 排查流程](#4.2 排查流程)

[4.3 磁盘排查总结](#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"

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

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

  2. 负载是哪来的?(vmstatr列还是b列高?wa有多高?)

  3. 最近有什么异常事件?(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显示的已用空间。

为什么dfdu结果对不上?

  • 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 killtruncate -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 确认连通性和端口状态

六、本篇小结

排查黄金法则

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

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

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

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

三个高频故障的完整链路

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

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

  • 磁盘隐形占用df vs du发现差异 → lsof +L1找(deleted)文件 → truncatekill -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篇的集群高可用,从最基础的cdls命令到内核函数级别的动态追踪。

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

相关推荐
A小辣椒4 小时前
TShark:基础知识
linux
AlfredZhao6 小时前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao21 小时前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334661 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪1 天前
linux 拷贝文件或目录到指定的位置
linux
大树882 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质2 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5202 天前
Linux 11 动态监控指令top
linux