凌晨两点,手机震动。监控系统突然告警:服务器响应延迟从50毫秒骤升至3秒,CPU使用率爆表。你打开终端,面对这台“生病”的机器,大脑一片空白,不知从何入手。这种窘境,几乎每位后端工程师都曾亲身经历。
本文提供一套可直接上手操作的排查流程,从收到报警到精准定位根因,覆盖CPU、内存、磁盘IO和网络四个维度。每一步用什么命令、看什么指标,都会为你详细拆解。

一、先建立全局视角:top / htop
收到报警后的第一件事,不是立刻猜测问题出在哪里,而是先看清系统整体状况。
top
top的输出信息虽然密集,但只需重点关注以下几个关键位置:

浏览完top的全局数据后,再根据哪个维度出现异常,进入对应的专项排查阶段。四个方向各有其独立的命令链路。
二、CPU异常排查
现象:CPU使用率居高不下,或系统负载(load average)远超CPU核心数。
第一步:找出消耗CPU最多的进程。
# top里按P键(默认按CPU排序)
top
# 或者用ps精确到进程
ps aux --sort=-%cpu | head -10
第二步:判断问题是出在用户态还是内核态。
查看top里%Cpu那行:
- us(user)高:表示业务逻辑占用大量CPU,或者存在死循环。
- sy(system)高:说明有大量系统调用,可能是频繁fork()、或IO操作过多。
- wa(iowait)高:代表CPU正在等待磁盘,此时瓶颈在IO而非CPU,应跳转到第四节排查。
第三步:定位到具体线程。
# 找出进程4521下哪个线程最耗CPU
top -H -p 4521
# 将线程TID转换为十六进制,便于对应gdb/jstack中的线程ID
printf '%x\n'
第四步:借助strace观察进程行为。
strace -p 4521 -c # 统计模式,查看哪个系统调用最频繁
常见CPU飙升的根因:
- 死循环(某个while条件永远为真)。
- 正则表达式的灾难性回溯。
- 频繁GC(Java程序Old Gen占满)。
- 大量并发请求导致上下文切换过于频繁。
三、内存异常排查
现象:内存使用率持续攀升,或OOM Killer频繁终止进程。
第一步:查看内存整体使用情况。
free -h
# 重点看available(真正可用的内存),不是free
# Swap被大量使用说明物理内存已经不足
cat /proc/meminfo | grep -E “MemTotal|MemFree|MemAvailable|Cached|SwapUsed”
第二步:找出内存占用最高的进程。
ps aux --sort=-%mem | head -10
# 查看某进程的详细内存分布
cat /proc//status | grep -E “VmRSS|VmSwap|VmSize”
第三步:判断是内存泄漏还是正常增长。
# 持续观察进程内存,每2秒刷新一次
watch -n 2 ‘cat /proc//status | grep VmRSS’
# 如果VmRSS一直上涨、从不下降,基本就是内存泄漏
第四步:检查OOM相关记录。
dmesg | grep -i “oom\|killed process” | tail -20
# 若出现OOM记录,说明已有进程被内核强制终止
常见内存问题根因:
- C/C++程序内存泄漏(malloc后未free)。
- Java堆内存配置过小,频繁触发Full GC。
- 内存碎片化严重(可换用jemalloc替代ptmalloc缓解)。
- 单个进程占用内存过大,挤压其他服务的生存空间。
四、磁盘IO异常排查
现象:服务响应变慢,CPU的wa指标偏高,磁盘读写出现告警。
第一步:确认IO是否为瓶颈。
iostat -x 1 5
# -x显示扩展指标,每1秒刷新一次,共5次
重点关注以下几列:

第二步:找出哪个进程在大量读写磁盘。
# 实时显示各进程的磁盘IO占用
iotop -o # -o仅显示有IO活动的进程
第三步:如果是日志或数据库写入导致的,检查Page Cache。
write()默认先写入Page Cache,由内核异步刷盘。如果程序频繁调用fsync(),每次都必须等待磁盘,此时await值会飙升。
# 查看脏页数量,数值很大说明积压了大量待刷数据
cat /proc/meminfo | grep -i dirty
常见IO问题根因:
- 数据库没有走索引,全表扫描产生大量随机IO。
- 日志级别过低(设为DEBUG),每条请求都写入日志。
- 未正确配置fsync策略,每次写入都需要同步等待磁盘。
- SSD写入放大效应(大量小块随机写入,可通过fio测试)。
五、网络异常排查
现象:接口超时、连接数打满、TIME_WAIT堆积。
第一步:查看整体网络连接状态。
# 统计各状态的TCP连接数
ss -s
# 详细查看当前所有连接
ss -tan | awk ‘{print $1}’ | sort | uniq -c | sort -rn
输出大致如下:
2341 TIME_WAIT ← 数量过多说明短连接过于频繁
512 ESTABLISHED ← 正常连接数
48 CLOSE_WAIT ← 数量多意味着有连接未正常关闭,代码存在bug
3 LISTEN
CLOSE_WAIT是一个特别值得关注的状态——它表示对端已经关闭连接(已发送FIN),但本端尚未调用close()。如果CLOSE_WAIT持续增长,通常是由于代码中没有正确关闭连接。
第二步:检查端口连接与带宽情况。
# 查看哪个端口的连接数最多
ss -tan | grep ESTABLISHED | awk ‘{print $5}’ | cut -d: -f2 | sort | uniq -c | sort -rn
# 实时监控网络带宽使用
iftop -n # 按连接维度查看
nethogs # 按进程维度查看(哪个进程在占用带宽)
第三步:排查丢包与延迟。
# 基本连通性和延迟测试
ping -c 10 <目标IP>
# 追踪路由路径,找出延迟在哪一跳
traceroute <目标IP>
# 抓包,真正了解数据在传输什么
tcpdump -i eth0 -w /tmp/capture.pcap port 8080
# 然后用Wireshark打开分析
第四步:检查内核网络参数。
# 查看连接队列是否溢出(SYN flood或并发过高时会满)
ss -lnt # 查看Send-Q列,如果持续非零说明队列已满
netstat -s | grep -i “listen\|overflow\|drop”
常见网络问题根因:
- TIME_WAIT过多导致端口耗尽(可开启SO_REUSEADDR并配合连接池解决)。
- CLOSE_WAIT积累(代码没有正确关闭连接)。
- 带宽被打满(大文件传输、日志采集等占满上行带宽)。
- DNS解析缓慢(每次请求都执行DNS查询且未启用缓存)。
六、四维排查的完整决策图
将上述排查流程整理成一张决策图,收到报警时直接对照查看:

七、一个完整的真实排查案例
下面把整个流程串联起来,看一个真实场景:
现象:下午3点接到报警,API响应时间从80ms暴涨到2秒,CPU告警。
第一步:用top观察全局。
load average: 12.3, 8.1, 5.2 ← 明显超过CPU核数(8核)
%Cpu: 42 us, 8 sy, 0 ni, 2 id, 45 wa ← wa=45%!IO等待极高
MiB Mem: 15832 total, 892 free
wa值高 → 问题不在CPU,而在IO。CPU正在等待磁盘。
第二步:用iostat确认IO瓶颈。
iostat -x 1
# sda r/s=2 w/s=4800 await=120ms %util=98%
%util=98%,磁盘已经跑满。每秒4800次写入,await高达120ms。
第三步:用iotop找出罪魁祸首。
PID DISK READ DISK WRITE COMMAND
4521 0.00 B/s 480 M/s java
Java进程正在疯狂写入磁盘,每秒480MB。
第四步:使用strace查看写入内容。
strace -p 4521 -e write 2>&1 | head -20
# write(3, “DEBUG 2024-03-15 …”, 256) = 256
# write(3, “DEBUG 2024-03-15 …”, 256) = 256
# …无限循环…
全部是DEBUG级别的日志,不断写入文件描述符fd=3。
根因:有人在生产环境开启了DEBUG日志,每个请求会写入十几条DEBUG日志,高峰期请求量一上来,直接打满磁盘IO。
修复:立即关闭DEBUG日志,CPU的wa从45%降至2%,响应时间恢复至80ms。
八、最常用命令速查表
收藏这份速查表,下次遇到报警直接翻阅:
# ===== 全局 =====
top # 实时全貌,按M内存排序,按P CPU排序
vmstat 1 # 每秒刷新,查看CPU/内存/IO综合指标
uptime # 快速查看load average
# ===== CPU =====
ps aux --sort=-%cpu | head # CPU占用TOP进程
top -H -p # 查看进程内线程的CPU占用
perf top # 函数级CPU热点(高级工具)
# ===== 内存 =====
free -h # 内存概况
ps aux --sort=-%mem | head # 内存占用TOP进程
cat /proc//status # 某进程内存详情
dmesg | grep oom # OOM历史记录
# ===== IO =====
iostat -x 1 # 磁盘IO详情
iotop -o # 查看哪个进程在读写
lsof +D /path # 查看谁打开了某目录下的文件
# ===== 网络 =====
ss -s # TCP连接状态统计
ss -tan | grep TIME_WAIT | wc -l # TIME_WAIT数量
iftop -n # 实时网络流量(按连接)
nethogs # 实时网络流量(按进程)
tcpdump -i eth0 port 8080 # 抓包
# ===== 通用深挖 =====
strace -p # 查看进程正在进行的系统调用
lsof -p # 查看进程打开了哪些文件/连接
九、写在最后
服务器排查从来不是靠运气,而是一套系统化的思维方法:先看全局(top)→ 判断哪个维度异常 → 定位异常进程 → 用strace/lsof观察其行为 → 结合内核原理解析根因。
本文涉及的所有工具和原理,在之前的系列文章中均有详细介绍:strace的使用方法、Page Cache为何影响IO性能、OOM Killer为何杀掉意料之外的进程、TIME_WAIT为何会堆积……一篇篇积累下来,排查时就能快速对号入座。
```