排查故障最消耗时间的,从来不是敲不出那条命令,而是不知道该敲哪一条。

很多人一遇到线上问题,第一反应就是”重启一下试试”。运气好能恢复,运气不好问题被掩盖,等下周再次爆发时,反而更难查。真正高效的排查,靠的不是命令数量,而是一套稳定的思考路径。

本文把日常最高频的排查动作,整理成一套可以直接套用的框架。

一、先做两件事:确认与止损

1. 确认问题真实存在

在动手排查之前,先回答一个问题:故障是真的,还是误报?

监控告警不等于故障。遇到过这种情况吗:Zabbix 报警说 CPU 飙到 90%,实际上一看是某个定时任务在正常跑批。没有确认就排查,等于在错误的方向上浪费时间。

确认的动作很简单,但经常被跳过:

1
2
3
4
5
6
7
8
# 1. 现象能稳定复现吗?还是偶发?
# 偶发问题要问清:多久一次?什么时间点?

# 2. 影响面有多大?单个用户、单个服务,还是全部?
# 影响面决定优先级,也决定你要不要立刻止损

# 3. 最近有没有变更?
# 变更往往是根因。发布、配置修改、依赖升级、硬件更换

排查现场的第一原则:先明确边界,再深入细节。 不知道问题范围的排查,等于大海捞针。

2. 必要时先止损

生产环境的排查有个残酷现实:有时候你来不及找到根因,但必须先恢复业务。

1
2
3
4
5
6
7
8
9
# 快速回滚是最高效的止损手段
# 查看最近10 次提交,快速定位可疑变更
git log --oneline -10

# 服务类:优雅重启,先恢复可用性
systemctl restart nginx

# 数据库类:注意先备份,不要盲目重启
mysqldump -u root -p db > /backup/db_$(date +%F).sql

止损和定位是两件事,不要混在一起。先恢复,再慢慢查原因——这对业务负责,也给自己争取了从容排查的时间。

二、分层定位:从下往上逐层排除

这是整套方法论的核心:按固定顺序自底向上排查,不要跳步。

1
2
3
4
5
6
7
7.业务层    ← 用户看到的是"打不开",但问题往往不在这里
6.应用层 ← 程序本身的 Bug、内存泄漏、GC 停顿
5.依赖层 ← 数据库、Redis、第三方接口是否正常
4.服务层 ← 进程是否存活、端口是否监听、日志是否报错
3.资源层 ← CPU、内存、IO、网络连接数
2.系统层 ← 磁盘空间、inode、内核参数、文件句柄
1.硬件层 ← 磁盘坏道、内存故障、网络抖动

为什么必须自底向上? 因为下层是上层的地基。数据库挂了导致应用报错,如果你先去看应用代码,会浪费大量时间;而实际上重启数据库就好了。

逐层的检查清单:

第 1-2 层:硬件与系统(最容易被忽略)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 磁盘空间和 inode(inode 耗尽会导致"磁盘还有空间却写不进去")
df -h
df -i

# 内存(重点看 available,不是 free)
free -h

# IO 等待(%util 高说明磁盘是瓶颈)
iostat -x 1 5 # 需 sysstat 包
iostat -x 1 5 | awk '$10+$12 > 50 {print "IO 繁忙:"$1}'

# CPU 负载(load average 要结合 CPU 核数看)
uptime
nproc
# load >>核数 说明任务在排队

排查技巧:top 里按 1 可以展开每个 CPU 核心,M 按内存排序,P 按 CPU 排序。这三个键几乎覆盖了 80% 的场景。

第 3-4 层:服务与依赖

1
2
3
4
5
6
7
8
9
10
# 服务状态与端口监听
systemctl status nginx
ss -lntp | grep :80

# 进程是否僵死(僵尸进程 Z)
ps -ef | awk '$8 ~ /Z/'

# 打开文件数是否触顶(配合 ulimit -n 排查)
ls /proc/$(pgrep -n nginx)/fd | wc -l
cat /proc/sys/fs/file-nr

依赖层是最容易出”玄学问题”的地方。一次接口超时,可能是自己的服务慢,也可能是下游数据库连接池打满了。先确定是谁的问题,再决定动谁。

第 5-6 层:应用与业务

到了这一层,通常意味着问题已经定位得比较窄了。这时候需要日志:

1
2
3
4
5
6
7
8
9
10
11
# 实时跟踪(最常用)
tail -f /var/log/nginx/error.log | grep -i error

# 过滤时间范围
grep "2026-10-19 14:3" app.log

# 统计高频错误(排查根因时非常有用)
grep -oP '(?<=ERROR ).*' app.log | sort | uniq -c | sort -rn | head

# 看崩溃转储
dmesg -T | grep -i "segfault\|oom\|error"

日志统计是个被严重低估的技巧。 面对几百行报错,直接看会懵;但把错误按类型聚合,出现次数最多的那一条,往往就是根因。

三、四条实战信号:快速缩小范围

如果时间紧张,记不住上面那套分层框架,下面这四个信号能帮你快速判断问题类型。

信号 1:load 很高但 CPU 空闲

1
2
uptime         # load 高
top # CPU 却很闲

结论:瓶颈在 IO 或磁盘,不在CPU。 常见于 NFS 卡顿、大量小文件读写、备份任务。

信号 2:内存持续增长不释放

1
2
free -h # available 持续下降
ps aux --sort=-%mem | head -10 # 找出元凶进程

结论:疑似内存泄漏。 短期先重启止损,长期要查代码或调 JVM 参数。

信号 3:连接数打满

1
2
3
ss -s                  # 查看连接统计
ss -ant | grep TIME-WAIT | wc -l
cat /proc/sys/net/ipv4/ip_local_port_range

结论:连接泄漏或没做连接池。 临时调大端口范围能缓解,根因要在应用层。

信号 4:日志里全是 timeout

结论:优先怀疑依赖服务,而不是本地服务。 先确认依赖是否健康——这是最快的排除法。

四、一份可复用的排查清单

把上面的内容压缩成可打印的清单,贴在工位上:

步骤 动作 关键命令
1 确认现象 复现条件、影响范围、最近变更
2 评估止损 是否需要先恢复业务
3 看系统资源 free -h df -h uptime
4 看磁盘 IO iostat -x 1 5
5 看服务状态 systemctl status ss -lntp
6 看连接数 ss -s ss -ant
7 看应用日志 tail -f dmesg -T
8 聚合错误 统计高频错误找根因
9 定位依赖 确认上下游是否健康
10 验证修复 复现步骤走一遍

五、几个能显著提速的习惯

1. 先看历史

1
2
3
# 上次同类问题怎么解决的?
grep -ri "端口占用\|内存不足\|磁盘满" /var/log/ | tail -20
history | grep -i "systemctl\|docker\|nginx"

同样的问题往往重复发生。 排查前先花五分钟问自己:”以前遇到过吗?”

2. 一次改一个变量

同时改三个配置再观察,等于放弃了归因能力。一次只动一个变量,改完立刻验证,这样才能确定哪个是根因。

3. 记录时间线

排查时随手记时间戳:

1
2
3
4
14:32监控报警 CPU 90%
14:35确认影响范围:3 个接口超时
14:40 定位到 14:20 有一次发布
14:45回滚后指标恢复 → 根因确认

故障复盘时,这份记录比任何记忆都可靠。排查完不记录的故障,下次还会再查一遍。

4. 别怕重启,但要会重启

在虚拟化环境下,重启几乎是零成本的止损手段。但要知道重启会破坏现场——所以:

  • 先抓现场:top -b -n 1 > /tmp/top.txt、dmesg > /tmp/dmesg.txt
  • 再重启
  • 保留日志,事后分析

结语

排查能力的差距,往往不在于记住多少命令,而在于有没有稳定的路径可循。

新人遇到故障容易慌,因为面对几百条报错不知道从哪看;老手之所以快,是因为他有一套固定的判断顺序——先确认、再止损,然后自底向上逐层排除。

把这篇文章的框架练成肌肉记忆,你排查故障的速度会提升一个量级。

框架是死的,实践是活的。下次遇到具体问题时,试着按这个顺序走一遍,你会发现很多”玄学问题”都能被解释。


相关阅读